diff options
author | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2011-11-30 11:36:13 -0600 |
---|---|---|
committer | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2011-11-30 11:36:13 -0600 |
commit | 664e37abfe5c796c1279b8295fb030f126b0a7d8 (patch) | |
tree | 85f4e661e5c615f01ee1cdf51ca1250b96efe315 /src/Editor.cpp | |
download | tqscintilla-664e37abfe5c796c1279b8295fb030f126b0a7d8.tar.gz tqscintilla-664e37abfe5c796c1279b8295fb030f126b0a7d8.zip |
Initial import of qscintilla from 2007
Diffstat (limited to 'src/Editor.cpp')
-rwxr-xr-x | src/Editor.cpp | 7299 |
1 files changed, 7299 insertions, 0 deletions
diff --git a/src/Editor.cpp b/src/Editor.cpp new file mode 100755 index 0000000..67440f1 --- /dev/null +++ b/src/Editor.cpp @@ -0,0 +1,7299 @@ +// Scintilla source code edit control +/** @file Editor.cxx + ** Main code for the edit control. + **/ +// Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#include "Platform.h" + +#ifndef PLAT_QT +#define INCLUDE_DEPRECATED_FEATURES +#endif +#include "Scintilla.h" + +#include "ContractionState.h" +#include "SVector.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 "Document.h" +#include "Editor.h" + +/* + 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) {} + +LineLayout::LineLayout(int maxLineLength_) : + lineStarts(0), + lenLineStarts(0), + lineNumber(-1), + inCache(false), + maxLineLength(-1), + numCharsInLine(0), + validity(llInvalid), + xHighlightGuide(0), + highlightColumn(0), + selStart(0), + selEnd(0), + containsCaret(false), + edgeColumn(0), + chars(0), + styles(0), + styleBitsSet(0), + indicators(0), + positions(0), + hsStart(0), + hsEnd(0), + widthLine(wrapWidthInfinite), + lines(1) { + Resize(maxLineLength_); +} + +LineLayout::~LineLayout() { + Free(); +} + +void LineLayout::Resize(int maxLineLength_) { + if (maxLineLength_ > maxLineLength) { + Free(); + chars = new char[maxLineLength_ + 1]; + styles = new unsigned char[maxLineLength_ + 1]; + indicators = new char[maxLineLength_ + 1]; + // Extra position allocated as sometimes the Windows + // GetTextExtentExPoint API writes an extra element. + positions = new int[maxLineLength_ + 1 + 1]; + maxLineLength = maxLineLength_; + } +} + +void LineLayout::Free() { + delete []chars; + chars = 0; + delete []styles; + styles = 0; + delete []indicators; + indicators = 0; + delete []positions; + positions = 0; + delete []lineStarts; + lineStarts = 0; +} + +void LineLayout::Invalidate(validLevel validity_) { + if (validity > validity_) + validity = validity_; +} + +void LineLayout::SetLineStart(int line, int start) { + if ((line >= lenLineStarts) && (line != 0)) { + int newMaxLines = line + 20; + int *newLineStarts = new int[newMaxLines]; + if (!newLineStarts) + return; + for (int i = 0; i < newMaxLines; i++) { + if (i < lenLineStarts) + newLineStarts[i] = lineStarts[i]; + else + newLineStarts[i] = 0; + } + delete []lineStarts; + lineStarts = newLineStarts; + lenLineStarts = newMaxLines; + } + lineStarts[line] = start; +} + +void LineLayout::SetBracesHighlight(Range rangeLine, Position braces[], + char bracesMatchStyle, int xHighlight) { + if (rangeLine.ContainsCharacter(braces[0])) { + int braceOffset = braces[0] - rangeLine.start; + if (braceOffset < numCharsInLine) { + bracePreviousStyles[0] = styles[braceOffset]; + styles[braceOffset] = bracesMatchStyle; + } + } + if (rangeLine.ContainsCharacter(braces[1])) { + int braceOffset = braces[1] - rangeLine.start; + if (braceOffset < numCharsInLine) { + bracePreviousStyles[1] = styles[braceOffset]; + styles[braceOffset] = bracesMatchStyle; + } + } + if ((braces[0] >= rangeLine.start && braces[1] <= rangeLine.end) || + (braces[1] >= rangeLine.start && braces[0] <= rangeLine.end)) { + xHighlightGuide = xHighlight; + } +} + +void LineLayout::RestoreBracesHighlight(Range rangeLine, Position braces[]) { + if (rangeLine.ContainsCharacter(braces[0])) { + int braceOffset = braces[0] - rangeLine.start; + if (braceOffset < numCharsInLine) { + styles[braceOffset] = bracePreviousStyles[0]; + } + } + if (rangeLine.ContainsCharacter(braces[1])) { + int braceOffset = braces[1] - rangeLine.start; + if (braceOffset < numCharsInLine) { + styles[braceOffset] = bracePreviousStyles[1]; + } + } + xHighlightGuide = 0; +} + +LineLayoutCache::LineLayoutCache() : + level(0), length(0), size(0), cache(0), + allInvalidated(false), styleClock(-1), useCount(0) { + Allocate(0); +} + +LineLayoutCache::~LineLayoutCache() { + Deallocate(); +} + +void LineLayoutCache::Allocate(int length_) { + PLATFORM_ASSERT(cache == NULL); + allInvalidated = false; + length = length_; + size = length; + if (size > 1) { + size = (size / 16 + 1) * 16; + } + if (size > 0) { + cache = new LineLayout * [size]; + } + for (int i = 0; i < size; i++) + cache[i] = 0; +} + +void LineLayoutCache::AllocateForLevel(int linesOnScreen, int linesInDoc) { + PLATFORM_ASSERT(useCount == 0); + int lengthForLevel = 0; + if (level == llcCaret) { + lengthForLevel = 1; + } else if (level == llcPage) { + lengthForLevel = linesOnScreen + 1; + } else if (level == llcDocument) { + lengthForLevel = linesInDoc; + } + if (lengthForLevel > size) { + Deallocate(); + Allocate(lengthForLevel); + } else { + if (lengthForLevel < length) { + for (int i = lengthForLevel; i < length; i++) { + delete cache[i]; + cache[i] = 0; + } + } + length = lengthForLevel; + } + PLATFORM_ASSERT(length == lengthForLevel); + PLATFORM_ASSERT(cache != NULL || length == 0); +} + +void LineLayoutCache::Deallocate() { + PLATFORM_ASSERT(useCount == 0); + for (int i = 0; i < length; i++) + delete cache[i]; + delete []cache; + cache = 0; + length = 0; + size = 0; +} + +void LineLayoutCache::Invalidate(LineLayout::validLevel validity_) { + if (cache && !allInvalidated) { + for (int i = 0; i < length; i++) { + if (cache[i]) { + cache[i]->Invalidate(validity_); + } + } + if (validity_ == LineLayout::llInvalid) { + allInvalidated = true; + } + } +} + +void LineLayoutCache::SetLevel(int level_) { + allInvalidated = false; + if ((level_ != -1) && (level != level_)) { + level = level_; + Deallocate(); + } +} + +LineLayout *LineLayoutCache::Retrieve(int lineNumber, int lineCaret, int maxChars, int styleClock_, + int linesOnScreen, int linesInDoc) { + AllocateForLevel(linesOnScreen, linesInDoc); + if (styleClock != styleClock_) { + Invalidate(LineLayout::llCheckTextAndStyle); + styleClock = styleClock_; + } + allInvalidated = false; + int pos = -1; + LineLayout *ret = 0; + if (level == llcCaret) { + pos = 0; + } else if (level == llcPage) { + if (lineNumber == lineCaret) { + pos = 0; + } else if (length > 1) { + pos = 1 + (lineNumber % (length - 1)); + } + } else if (level == llcDocument) { + pos = lineNumber; + } + if (pos >= 0) { + PLATFORM_ASSERT(useCount == 0); + if (cache && (pos < length)) { + if (cache[pos]) { + if ((cache[pos]->lineNumber != lineNumber) || + (cache[pos]->maxLineLength < maxChars)) { + delete cache[pos]; + cache[pos] = 0; + } + } + if (!cache[pos]) { + cache[pos] = new LineLayout(maxChars); + } + if (cache[pos]) { + cache[pos]->lineNumber = lineNumber; + cache[pos]->inCache = true; + ret = cache[pos]; + useCount++; + } + } + } + + if (!ret) { + ret = new LineLayout(maxChars); + ret->lineNumber = lineNumber; + } + + return ret; +} + +void LineLayoutCache::Dispose(LineLayout *ll) { + allInvalidated = false; + if (ll) { + if (!ll->inCache) { + delete ll; + } else { + useCount--; + } + } +} + +Editor::Editor() { + ctrlID = 0; + + stylesValid = false; + + printMagnification = 0; + printColourMode = SC_PRINT_NORMAL; + printWrapState = eWrapWord; + cursorMode = SC_CURSORNORMAL; + controlCharSymbol = 0; /* Draw the control characters */ + + hasFocus = false; + hideSelection = false; + inOverstrike = false; + 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 = false; + dropWentOutside = false; + posDrag = invalidPosition; + posDrop = invalidPosition; + selectionType = selChar; + + lastXChosen = 0; + lineAnchor = 0; + originalAnchorPos = 0; + + selType = selStream; + moveExtendsSelection = false; + xStartSelect = 0; + xEndSelect = 0; + primarySelection = true; + + caretXPolicy = CARET_SLOP | CARET_EVEN; + caretXSlop = 50; + + caretYPolicy = CARET_EVEN; + caretYSlop = 0; + + searchAnchor = 0; + + xOffset = 0; + xCaretMargin = 50; + horizontalScrollBarVisible = true; + scrollWidth = 2000; + verticalScrollBarVisible = true; + endAtLastLine = true; + caretSticky = false; + + pixmapLine = Surface::Allocate(); + pixmapSelMargin = Surface::Allocate(); + pixmapSelPattern = Surface::Allocate(); + pixmapIndentGuide = Surface::Allocate(); + pixmapIndentGuideHighlight = Surface::Allocate(); + + currentPos = 0; + anchor = 0; + + targetStart = 0; + targetEnd = 0; + searchFlags = 0; + + topLine = 0; + posTopLine = 0; + + lengthForEncode = -1; + + needUpdateUI = true; + braces[0] = invalidPosition; + braces[1] = invalidPosition; + bracesMatchStyle = STYLE_BRACEBAD; + highlightGuideColumn = 0; + + theEdge = 0; + + paintState = notPainting; + + modEventMask = SC_MODEVENTMASKALL; + + pdoc = new Document(); + pdoc->AddRef(); + pdoc->AddWatcher(this, 0); + + recordingMacro = false; + foldFlags = 0; + + wrapState = eWrapNone; + wrapWidth = LineLayout::wrapWidthInfinite; + wrapStart = wrapLineLarge; + wrapEnd = wrapLineLarge; + wrapVisualFlags = 0; + wrapVisualFlagsLocation = 0; + wrapVisualStartIndent = 0; + actualWrapVisualStartIndent = 0; + + convertPastes = true; + + hsStart = -1; + hsEnd = -1; + + llc.SetLevel(LineLayoutCache::llcCaret); +} + +Editor::~Editor() { + pdoc->RemoveWatcher(this, 0); + pdoc->Release(); + pdoc = 0; + DropGraphics(); + delete pixmapLine; + delete pixmapSelMargin; + delete pixmapSelPattern; + delete pixmapIndentGuide; + delete pixmapIndentGuideHighlight; +} + +void Editor::Finalise() { + SetIdle(false); + CancelModes(); +} + +void Editor::DropGraphics() { + pixmapLine->Release(); + pixmapSelMargin->Release(); + pixmapSelPattern->Release(); + pixmapIndentGuide->Release(); + pixmapIndentGuideHighlight->Release(); +} + +void Editor::InvalidateStyleData() { + stylesValid = false; + palette.Release(); + DropGraphics(); + llc.Invalidate(LineLayout::llInvalid); + if (selType == selRectangle) { + xStartSelect = XFromPosition(anchor); + xEndSelect = XFromPosition(currentPos); + } +} + +void Editor::InvalidateStyleRedraw() { + NeedWrapping(); + InvalidateStyleData(); + Redraw(); +} + +void Editor::RefreshColourPalette(Palette &pal, bool want) { + vs.RefreshColourPalette(pal, want); +} + +void Editor::RefreshStyleData() { + if (!stylesValid) { + stylesValid = true; + AutoSurface surface(this); + if (surface) { + vs.Refresh(*surface); + RefreshColourPalette(palette, true); + palette.Allocate(wMain); + RefreshColourPalette(palette, false); + } + SetScrollBars(); + } +} + +PRectangle Editor::GetClientRectangle() { + return wMain.GetClientPosition(); +} + +PRectangle Editor::GetTextRectangle() { + PRectangle rc = GetClientRectangle(); + rc.left += vs.fixedColumnWidth; + 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; + } +} + +static inline bool IsControlCharacter(int ch) { + // iscntrl returns true for lots of chars > 127 which are displayable + return ch >= 0 && ch < ' '; +} + +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 &) { return * this; } +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_; + } +}; + +/** + * Allows to iterate through the lines of a selection. + * Althought it can be called for a stream selection, in most cases + * it is inefficient and it should be used only for + * a rectangular or a line selection. + */ +class SelectionLineIterator { +private: + Editor *ed; + int line; ///< Current line within the iteration. + bool forward; ///< True if iterating by increasing line number, false otherwise. + int selStart, selEnd; ///< Positions of the start and end of the selection relative to the start of the document. + int minX, maxX; ///< Left and right of selection rectangle. + +public: + int lineStart, lineEnd; ///< Line numbers, first and last lines of the selection. + int startPos, endPos; ///< Positions of the beginning and end of the selection on the current line. + + void Reset() { + if (forward) { + line = lineStart; + } else { + line = lineEnd; + } + } + + SelectionLineIterator(Editor *ed_, bool forward_ = true) : line(0), startPos(0), endPos(0) { + ed = ed_; + forward = forward_; + selStart = ed->SelectionStart(); + selEnd = ed->SelectionEnd(); + lineStart = ed->pdoc->LineFromPosition(selStart); + lineEnd = ed->pdoc->LineFromPosition(selEnd); + // Left of rectangle + minX = Platform::Minimum(ed->xStartSelect, ed->xEndSelect); + // Right of rectangle + maxX = Platform::Maximum(ed->xStartSelect, ed->xEndSelect); + Reset(); + } + ~SelectionLineIterator() {} + + void SetAt(int line) { + if (line < lineStart || line > lineEnd) { + startPos = endPos = INVALID_POSITION; + } else { + if (ed->selType == ed->selRectangle) { + // Measure line and return character closest to minX + startPos = ed->PositionFromLineX(line, minX); + // Measure line and return character closest to maxX + endPos = ed->PositionFromLineX(line, maxX); + } else if (ed->selType == ed->selLines) { + startPos = ed->pdoc->LineStart(line); + endPos = ed->pdoc->LineStart(line + 1); + } else { // Stream selection, here only for completion + if (line == lineStart) { + startPos = selStart; + } else { + startPos = ed->pdoc->LineStart(line); + } + if (line == lineEnd) { + endPos = selEnd; + } else { + endPos = ed->pdoc->LineStart(line + 1); + } + } + } + } + bool Iterate() { + SetAt(line); + if (forward) { + line++; + } else { + line--; + } + return startPos != INVALID_POSITION; + } +}; + +Point Editor::LocationFromPosition(int pos) { + Point pt; + RefreshStyleData(); + if (pos == INVALID_POSITION) + return pt; + int line = pdoc->LineFromPosition(pos); + 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 - 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 (actualWrapVisualStartIndent != 0) { + int lineStart = ll->LineStart(subLine); + if (lineStart != 0) // Wrapped + pt.x += actualWrapVisualStartIndent * vs.aveCharWidth; + } + } + if (posInLine >= ll->LineStart(subLine)) { + pt.y += vs.lineHeight; + } + } + pt.x += vs.fixedColumnWidth - xOffset; + } + return pt; +} + +int Editor::XFromPosition(int pos) { + Point pt = LocationFromPosition(pos); + return pt.x - vs.fixedColumnWidth + xOffset; +} + +int Editor::LineFromLocation(Point pt) { + return cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); +} + +void Editor::SetTopLine(int topLineNew) { + topLine = topLineNew; + posTopLine = pdoc->LineStart(cs.DocFromDisplay(topLine)); +} + +static inline bool IsEOLChar(char ch) { + return (ch == '\r') || (ch == '\n'); +} + +int Editor::PositionFromLocation(Point pt) { + RefreshStyleData(); + pt.x = pt.x - vs.fixedColumnWidth + xOffset; + int visibleLine = pt.y / vs.lineHeight + topLine; + if (pt.y < 0) { // Division rounds towards 0 + visibleLine = (pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine; + } + if (visibleLine < 0) + visibleLine = 0; + int lineDoc = cs.DocFromDisplay(visibleLine); + if (lineDoc >= pdoc->LinesTotal()) + return pdoc->Length(); + unsigned int posLineStart = pdoc->LineStart(lineDoc); + int retVal = 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->LineStart(subLine + 1); + int subLineStart = ll->positions[lineStart]; + + if (actualWrapVisualStartIndent != 0) { + if (lineStart != 0) // Wrapped + pt.x -= actualWrapVisualStartIndent * vs.aveCharWidth; + } + for (int i = lineStart; i < lineEnd; i++) { + if (pt.x < (((ll->positions[i] + ll->positions[i + 1]) / 2) - subLineStart) || + IsEOLChar(ll->chars[i])) { + return pdoc->MovePositionOutsideChar(i + posLineStart, 1); + } + } + return lineEnd + posLineStart; + } + retVal = ll->numCharsInLine + posLineStart; + } + return retVal; +} + +// Like PositionFromLocation but INVALID_POSITION returned when not near any text. +int Editor::PositionFromLocationClose(Point pt) { + RefreshStyleData(); + PRectangle rcClient = GetTextRectangle(); + if (!rcClient.Contains(pt)) + return INVALID_POSITION; + if (pt.x < vs.fixedColumnWidth) + return INVALID_POSITION; + if (pt.y < 0) + return INVALID_POSITION; + pt.x = pt.x - vs.fixedColumnWidth + xOffset; + int visibleLine = pt.y / vs.lineHeight + topLine; + if (pt.y < 0) { // Division rounds towards 0 + visibleLine = (pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine; + } + int lineDoc = cs.DocFromDisplay(visibleLine); + if (lineDoc < 0) + return INVALID_POSITION; + if (lineDoc >= pdoc->LinesTotal()) + return INVALID_POSITION; + AutoSurface surface(this); + AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); + if (surface && ll) { + LayoutLine(lineDoc, surface, vs, ll, wrapWidth); + unsigned int posLineStart = pdoc->LineStart(lineDoc); + int lineStartSet = cs.DisplayFromDoc(lineDoc); + int subLine = visibleLine - lineStartSet; + if (subLine < ll->lines) { + int lineStart = ll->LineStart(subLine); + int lineEnd = ll->LineStart(subLine + 1); + int subLineStart = ll->positions[lineStart]; + + if (actualWrapVisualStartIndent != 0) { + if (lineStart != 0) // Wrapped + pt.x -= actualWrapVisualStartIndent * vs.aveCharWidth; + } + for (int i = lineStart; i < lineEnd; i++) { + if (pt.x < (((ll->positions[i] + ll->positions[i + 1]) / 2) - subLineStart) || + IsEOLChar(ll->chars[i])) { + return pdoc->MovePositionOutsideChar(i + posLineStart, 1); + } + } + if (pt.x < (ll->positions[lineEnd] - subLineStart)) { + return pdoc->MovePositionOutsideChar(lineEnd + posLineStart, 1); + } + } + } + + return INVALID_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. + */ +int Editor::PositionFromLineX(int lineDoc, int x) { + RefreshStyleData(); + if (lineDoc >= pdoc->LinesTotal()) + return 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); + retVal = ll->numCharsInLine + posLineStart; + int subLine = 0; + int lineStart = ll->LineStart(subLine); + int lineEnd = ll->LineStart(subLine + 1); + int subLineStart = ll->positions[lineStart]; + + if (actualWrapVisualStartIndent != 0) { + if (lineStart != 0) // Wrapped + x -= actualWrapVisualStartIndent * vs.aveCharWidth; + } + for (int i = lineStart; i < lineEnd; i++) { + if (x < (((ll->positions[i] + ll->positions[i + 1]) / 2) - subLineStart) || + IsEOLChar(ll->chars[i])) { + retVal = pdoc->MovePositionOutsideChar(i + posLineStart, 1); + break; + } + } + } + return retVal; +} + +/** + * 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); + //wMain.InvalidateAll(); +} + +void Editor::RedrawSelMargin(int line) { + if (!AbandonPaint()) { + if (vs.maskInLine) { + Redraw(); + } else { + PRectangle rcSelMargin = GetClientRectangle(); + rcSelMargin.right = vs.fixedColumnWidth; + if (line != -1) { + int position = pdoc->LineStart(line); + PRectangle rcLine = RectangleFromRange(position, position); + rcSelMargin.top = rcLine.top; + rcSelMargin.bottom = rcLine.bottom; + } + 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; + rc.left = vs.fixedColumnWidth; + rc.top = (minLine - topLine) * vs.lineHeight; + if (rc.top < 0) + rc.top = 0; + rc.right = rcClient.right; + rc.bottom = (maxLine - topLine + 1) * vs.lineHeight; + // Ensure PRectangle is within 16 bit space + rc.top = Platform::Clamp(rc.top, -32000, 32000); + rc.bottom = Platform::Clamp(rc.bottom, -32000, 32000); + + return rc; +} + +void Editor::InvalidateRange(int start, int end) { + RedrawRect(RectangleFromRange(start, end)); +} + +int Editor::CurrentPosition() { + return currentPos; +} + +bool Editor::SelectionEmpty() { + return anchor == currentPos; +} + +int Editor::SelectionStart() { + return Platform::Minimum(currentPos, anchor); +} + +int Editor::SelectionEnd() { + return Platform::Maximum(currentPos, anchor); +} + +void Editor::SetRectangularRange() { + if (selType == selRectangle) { + xStartSelect = XFromPosition(anchor); + xEndSelect = XFromPosition(currentPos); + } +} + +void Editor::InvalidateSelection(int currentPos_, int anchor_) { + int firstAffected = anchor; + if (firstAffected > currentPos) + firstAffected = currentPos; + if (firstAffected > anchor_) + firstAffected = anchor_; + if (firstAffected > currentPos_) + firstAffected = currentPos_; + int lastAffected = anchor; + if (lastAffected < currentPos) + lastAffected = currentPos; + if (lastAffected < anchor_) + lastAffected = anchor_; + if (lastAffected < (currentPos_ + 1)) // +1 ensures caret repainted + lastAffected = (currentPos_ + 1); + needUpdateUI = true; + InvalidateRange(firstAffected, lastAffected); +} + +void Editor::SetSelection(int currentPos_, int anchor_) { + currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); + anchor_ = pdoc->ClampPositionIntoDocument(anchor_); + if ((currentPos != currentPos_) || (anchor != anchor_)) { + InvalidateSelection(currentPos_, anchor_); + currentPos = currentPos_; + anchor = anchor_; + } + SetRectangularRange(); + ClaimSelection(); +} + +void Editor::SetSelection(int currentPos_) { + currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); + if (currentPos != currentPos_) { + InvalidateSelection(currentPos_, currentPos_); + currentPos = currentPos_; + } + SetRectangularRange(); + ClaimSelection(); +} + +void Editor::SetEmptySelection(int currentPos_) { + selType = selStream; + moveExtendsSelection = false; + SetSelection(currentPos_, 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() { + // DONE, but untested...: make support rectangular selection + bool scp = false; + if (selType == selStream) { + scp = RangeContainsProtected(anchor, currentPos); + } else { + SelectionLineIterator lineIterator(this); + while (lineIterator.Iterate()) { + if (RangeContainsProtected(lineIterator.startPos, lineIterator.endPos)) { + scp = true; + break; + } + } + } + return scp; +} + +/** + * Asks document to find a good position and then moves out of any invisible positions. + */ +int Editor::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) { + pos = pdoc->MovePositionOutsideChar(pos, moveDir, checkLineEnd); + if (vs.ProtectionActive()) { + int mask = pdoc->stylingBitsMask; + if (moveDir > 0) { + if ((pos > 0) && vs.styles[pdoc->StyleAt(pos - 1) & mask].IsProtected()) { + while ((pos < pdoc->Length()) && + (vs.styles[pdoc->StyleAt(pos) & mask].IsProtected())) + pos++; + } + } else if (moveDir < 0) { + if (vs.styles[pdoc->StyleAt(pos) & mask].IsProtected()) { + while ((pos > 0) && + (vs.styles[pdoc->StyleAt(pos - 1) & mask].IsProtected())) + pos--; + } + } + } + return pos; +} + +int Editor::MovePositionTo(int newPos, selTypes sel, bool ensureVisible) { + int delta = newPos - currentPos; + newPos = pdoc->ClampPositionIntoDocument(newPos); + newPos = MovePositionOutsideChar(newPos, delta); + if (sel != noSel) { + selType = sel; + } + if (sel != noSel || moveExtendsSelection) { + SetSelection(newPos); + } else { + SetEmptySelection(newPos); + } + ShowCaretAtCurrentPosition(); + if (ensureVisible) { + EnsureCaretVisible(); + } + NotifyMove(newPos); + return 0; +} + +int Editor::MovePositionSoVisible(int pos, int moveDir) { + pos = pdoc->ClampPositionIntoDocument(pos); + pos = MovePositionOutsideChar(pos, moveDir); + int lineDoc = pdoc->LineFromPosition(pos); + 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 pdoc->LineStart(cs.DocFromDisplay(lineDisplay)); + } else { + lineDisplay = Platform::Clamp(lineDisplay - 1, 0, cs.LinesDisplayed()); + return pdoc->LineEnd(cs.DocFromDisplay(lineDisplay)); + } + } +} + +/** + * Choose the x position that the caret will try to stick to + * as it moves up and down. + */ +void Editor::SetLastXChosen() { + Point pt = LocationFromPosition(currentPos); + lastXChosen = pt.x; +} + +void Editor::ScrollTo(int line, bool moveThumb) { + int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); + if (topLineNew != topLine) { + // Try to optimise small scrolls + int linesToMove = topLine - topLineNew; + SetTopLine(topLineNew); + ShowCaretAtCurrentPosition(); + // Perform redraw rather than scroll if many lines would be redrawn anyway. +#ifndef UNDER_CE + if (abs(linesToMove) <= 10) { + ScrollText(linesToMove); + } else { + Redraw(); + } +#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 ((wrapState == eWrapNone) && (xOffset != xPos)) { + xOffset = xPos; + SetHorizontalScrollPos(); + RedrawRect(GetClientRectangle()); + } +} + +void Editor::MoveCaretInsideView(bool ensureVisible) { + PRectangle rcClient = GetTextRectangle(); + Point pt = LocationFromPosition(currentPos); + if (pt.y < rcClient.top) { + MovePositionTo(PositionFromLocation( + Point(lastXChosen, rcClient.top)), + noSel, ensureVisible); + } else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { + int yOfLastLineFullyDisplayed = rcClient.top + (LinesOnScreen() - 1) * vs.lineHeight; + MovePositionTo(PositionFromLocation( + Point(lastXChosen, rcClient.top + yOfLastLineFullyDisplayed)), + 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 +*/ +void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { + //Platform::DebugPrintf("EnsureCaretVisible %d %s\n", xOffset, useMargin ? " margin" : " "); + PRectangle rcClient = GetTextRectangle(); + //int rcClientFullWidth = rcClient.Width(); + int posCaret = currentPos; + if (posDrag >= 0) { + posCaret = posDrag; + } + Point pt = LocationFromPosition(posCaret); + Point ptBottomCaret = pt; + ptBottomCaret.y += vs.lineHeight - 1; + int lineCaret = DisplayFromPosition(posCaret); + bool bSlop, bStrict, bJump, bEven; + + // Vertical positioning + if (vert && (pt.y < rcClient.top || ptBottomCaret.y > rcClient.bottom || (caretYPolicy & CARET_STRICT) != 0)) { + int linesOnScreen = LinesOnScreen(); + int halfScreen = Platform::Maximum(linesOnScreen - 1, 2) / 2; + int newTopLine = topLine; + bSlop = (caretYPolicy & CARET_SLOP) != 0; + bStrict = (caretYPolicy & CARET_STRICT) != 0; + bJump = (caretYPolicy & CARET_JUMPS) != 0; + 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 (!useMargin) { + // 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 + newTopLine = lineCaret - yMoveT; + } else if (lineCaret > topLine + linesOnScreen - 1 - yMarginB) { + // Caret goes too low + newTopLine = 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 + newTopLine = lineCaret - yMoveT; + } else if (lineCaret > topLine + linesOnScreen - 1) { + // Caret goes too low + newTopLine = lineCaret - linesOnScreen + 1 + yMoveB; + } + } + } else { // No slop + if (!bStrict && !bJump) { + // Minimal move + if (lineCaret < topLine) { + // Caret goes too high + newTopLine = lineCaret; + } else if (lineCaret > topLine + linesOnScreen - 1) { + // Caret goes too low + if (bEven) { + newTopLine = lineCaret - linesOnScreen + 1; + } else { + newTopLine = lineCaret; + } + } + } else { // Strict or going out of display + if (bEven) { + // Always center caret + newTopLine = lineCaret - halfScreen; + } else { + // Always put caret on top of display + newTopLine = lineCaret; + } + } + } + newTopLine = Platform::Clamp(newTopLine, 0, MaxScrollPos()); + if (newTopLine != topLine) { + Redraw(); + SetTopLine(newTopLine); + SetVerticalScrollPos(); + } + } + + // Horizontal positioning + if (horiz && (wrapState == eWrapNone)) { + int halfScreen = Platform::Maximum(rcClient.Width() - 4, 4) / 2; + int xOffsetNew = xOffset; + bSlop = (caretXPolicy & CARET_SLOP) != 0; + bStrict = (caretXPolicy & CARET_STRICT) != 0; + bJump = (caretXPolicy & CARET_JUMPS) != 0; + bEven = (caretXPolicy & CARET_EVEN) != 0; + + if (bSlop) { // A margin is defined + int xMoveL, xMoveR; + if (bStrict) { + int xMarginL, xMarginR; + if (!useMargin) { + // 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) { + xOffsetNew -= xMoveL; + } else { + // Move just enough to allow to display the caret + xOffsetNew -= (rcClient.left + xMarginL) - pt.x; + } + } else if (pt.x >= rcClient.right - xMarginR) { + // Caret is on the right of the display + if (bJump && bEven) { + xOffsetNew += xMoveR; + } else { + // Move just enough to allow to display the caret + xOffsetNew += 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 + xOffsetNew -= xMoveL; + } else if (pt.x >= rcClient.right) { + // Caret is on the right of the display + xOffsetNew += 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 + xOffsetNew += pt.x - rcClient.left - halfScreen; + } else { + // Put caret on right + xOffsetNew += 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) { + xOffsetNew -= rcClient.left - pt.x; + } else { + xOffsetNew += pt.x - rcClient.right + 1; + } + } else if (pt.x >= rcClient.right) { + // Caret is on the right of the display + xOffsetNew += 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 + xOffsetNew) { + xOffsetNew = pt.x + xOffset - rcClient.left; + } else if (pt.x + xOffset >= rcClient.right + xOffsetNew) { + xOffsetNew = pt.x + xOffset - rcClient.right + 1; + } + if (xOffsetNew < 0) { + xOffsetNew = 0; + } + if (xOffset != xOffsetNew) { + xOffset = xOffsetNew; + if (xOffsetNew > 0) { + PRectangle rcText = GetTextRectangle(); + if (horizontalScrollBarVisible == true && + rcText.Width() + xOffset > scrollWidth) { + scrollWidth = xOffset + rcText.Width(); + SetScrollBars(); + } + } + SetHorizontalScrollPos(); + Redraw(); + } + } + UpdateSystemCaret(); +} + +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 >= 0) + InvalidateRange(posDrag, posDrag + 1); + else + InvalidateRange(currentPos, currentPos + 1); + UpdateSystemCaret(); +} + +void Editor::UpdateSystemCaret() { +} + +void Editor::NeedWrapping(int docLineStart, int docLineEnd) { + docLineStart = Platform::Clamp(docLineStart, 0, pdoc->LinesTotal()); + if (wrapStart > docLineStart) { + wrapStart = docLineStart; + llc.Invalidate(LineLayout::llPositions); + } + if (wrapEnd < docLineEnd) { + wrapEnd = docLineEnd; + } + wrapEnd = Platform::Clamp(wrapEnd, 0, pdoc->LinesTotal()); + // Wrap lines during idle. + if ((wrapState != eWrapNone) && (wrapEnd != wrapStart)) { + SetIdle(true); + } +} + +// Check if wrapping needed and perform any needed wrapping. +// fullwrap: if true, all lines which need wrapping will be done, +// in this single call. +// priorityWrapLineStart: If greater than zero, all lines starting from +// here to 1 page + 100 lines past will be wrapped (even if there are +// more lines under wrapping process in idle). +// If it is neither fullwrap, nor priorityWrap, then 1 page + 100 lines will be +// wrapped, if there are any wrapping going on in idle. (Generally this +// condition is called only from idler). +// Return true if wrapping occurred. +bool Editor::WrapLines(bool fullWrap, int priorityWrapLineStart) { + // If there are any pending wraps, do them during idle if possible. + int linesInOneCall = LinesOnScreen() + 100; + if (wrapState != eWrapNone) { + if (wrapStart < wrapEnd) { + if (!SetIdle(true)) { + // Idle processing not supported so full wrap required. + fullWrap = true; + } + } + if (!fullWrap && priorityWrapLineStart >= 0 && + // .. and if the paint window is outside pending wraps + (((priorityWrapLineStart + linesInOneCall) < wrapStart) || + (priorityWrapLineStart > wrapEnd))) { + // No priority wrap pending + return false; + } + } + int goodTopLine = topLine; + bool wrapOccurred = false; + if (wrapStart <= pdoc->LinesTotal()) { + if (wrapState == eWrapNone) { + if (wrapWidth != LineLayout::wrapWidthInfinite) { + wrapWidth = LineLayout::wrapWidthInfinite; + for (int lineDoc = 0; lineDoc < pdoc->LinesTotal(); lineDoc++) { + cs.SetHeight(lineDoc, 1); + } + wrapOccurred = true; + } + wrapStart = wrapLineLarge; + wrapEnd = wrapLineLarge; + } else { + if (wrapEnd >= pdoc->LinesTotal()) + wrapEnd = pdoc->LinesTotal(); + //ElapsedTime et; + int lineDocTop = cs.DocFromDisplay(topLine); + int subLineTop = topLine - cs.DisplayFromDoc(lineDocTop); + PRectangle rcTextArea = GetClientRectangle(); + rcTextArea.left = vs.fixedColumnWidth; + rcTextArea.right -= vs.rightMarginWidth; + wrapWidth = rcTextArea.Width(); + // Ensure all of the document is styled. + pdoc->EnsureStyledTo(pdoc->Length()); + RefreshStyleData(); + AutoSurface surface(this); + if (surface) { + bool priorityWrap = false; + int lastLineToWrap = wrapEnd; + int lineToWrap = wrapStart; + if (!fullWrap) { + if (priorityWrapLineStart >= 0) { + // This is a priority wrap. + lineToWrap = priorityWrapLineStart; + lastLineToWrap = priorityWrapLineStart + linesInOneCall; + priorityWrap = true; + } else { + // This is idle wrap. + lastLineToWrap = wrapStart + linesInOneCall; + } + if (lastLineToWrap >= wrapEnd) + lastLineToWrap = wrapEnd; + } // else do a fullWrap. + + // Platform::DebugPrintf("Wraplines: full = %d, priorityStart = %d (wrapping: %d to %d)\n", fullWrap, priorityWrapLineStart, lineToWrap, lastLineToWrap); + // Platform::DebugPrintf("Pending wraps: %d to %d\n", wrapStart, wrapEnd); + while (lineToWrap < lastLineToWrap) { + AutoLineLayout ll(llc, RetrieveLineLayout(lineToWrap)); + int linesWrapped = 1; + if (ll) { + LayoutLine(lineToWrap, surface, vs, ll, wrapWidth); + linesWrapped = ll->lines; + } + if (cs.SetHeight(lineToWrap, linesWrapped)) { + wrapOccurred = true; + } + lineToWrap++; + } + if (!priorityWrap) + wrapStart = lineToWrap; + // If wrapping is done, bring it to resting position + if (wrapStart >= wrapEnd) { + wrapStart = wrapLineLarge; + wrapEnd = wrapLineLarge; + } + } + goodTopLine = cs.DisplayFromDoc(lineDocTop); + if (subLineTop < cs.GetHeight(lineDocTop)) + goodTopLine += subLineTop; + else + goodTopLine += cs.GetHeight(lineDocTop); + //double durWrap = et.Duration(true); + //Platform::DebugPrintf("Wrap:%9.6g \n", durWrap); + } + } + if (wrapOccurred) { + SetScrollBars(); + SetTopLine(Platform::Clamp(goodTopLine, 0, MaxScrollPos())); + SetVerticalScrollPos(); + } + return wrapOccurred; +} + +void Editor::LinesJoin() { + if (!RangeContainsProtected(targetStart, targetEnd)) { + pdoc->BeginUndoAction(); + bool prevNonWS = true; + for (int pos = targetStart; pos < targetEnd; pos++) { + if (IsEOLChar(pdoc->CharAt(pos))) { + targetEnd -= pdoc->LenChar(pos); + pdoc->DelChar(pos); + if (prevNonWS) { + // Ensure at least one space separating previous lines + pdoc->InsertChar(pos, ' '); + } + } else { + prevNonWS = pdoc->CharAt(pos) != ' '; + } + } + pdoc->EndUndoAction(); + } +} + +const char *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); + pdoc->BeginUndoAction(); + 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->InsertString(posLineStart + (subLine - 1) * strlen(eol) + + ll->LineStart(subLine), eol); + targetEnd += static_cast<int>(strlen(eol)); + } + } + lineEnd = pdoc->LineFromPosition(targetEnd); + } + pdoc->EndUndoAction(); + } +} + +int Editor::SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) { + if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) + return markerDefault; + return markerCheck; +} + +// Avoid 64 bit compiler warnings. +// Scintilla does not support text buffers larger than 2**31 +static int istrlen(const char *s) { + return static_cast<int>(strlen(s)); +} + +void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { + if (vs.fixedColumnWidth == 0) + return; + + PRectangle rcMargin = GetClientRectangle(); + rcMargin.right = vs.fixedColumnWidth; + + if (!rc.Intersects(rcMargin)) + return; + + Surface *surface; + if (bufferedDraw) { + surface = pixmapSelMargin; + } else { + surface = surfWindow; + } + + PRectangle rcSelMargin = rcMargin; + rcSelMargin.right = rcMargin.left; + + for (int margin = 0; margin < vs.margins; 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) { + /* alternate scheme: + if (vs.ms[margin].mask & SC_MASK_FOLDERS) + surface->FillRectangle(rcSelMargin, vs.styles[STYLE_DEFAULT].back.allocated); + else + // Required because of special way brush is created for selection margin + surface->FillRectangle(rcSelMargin, pixmapSelPattern); + */ + if (vs.ms[margin].mask & SC_MASK_FOLDERS) + // Required because of special way brush is created for selection margin + surface->FillRectangle(rcSelMargin, *pixmapSelPattern); + else { + ColourAllocated colour; + switch (vs.ms[margin].style) { + case SC_MARGIN_BACK: + colour = vs.styles[STYLE_DEFAULT].back.allocated; + break; + case SC_MARGIN_FORE: + colour = vs.styles[STYLE_DEFAULT].fore.allocated; + break; + default: + colour = vs.styles[STYLE_LINENUMBER].back.allocated; + break; + } + surface->FillRectangle(rcSelMargin, colour); + } + } else { + surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back.allocated); + } + + int visibleLine = topLine; + int yposScreen = 0; + + // 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; + int level = pdoc->GetLevel(cs.DocFromDisplay(topLine)); + if (level & SC_FOLDLEVELWHITEFLAG) { + int lineBack = cs.DocFromDisplay(topLine); + 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; + } + } + + // 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 < rcMargin.bottom) { + + PLATFORM_ASSERT(visibleLine < cs.LinesDisplayed()); + + int lineDoc = cs.DocFromDisplay(visibleLine); + PLATFORM_ASSERT(cs.GetVisible(lineDoc)); + bool firstSubLine = visibleLine == cs.DisplayFromDoc(lineDoc); + + // Decide which fold indicator should be displayed + level = pdoc->GetLevel(lineDoc); + int levelNext = pdoc->GetLevel(lineDoc + 1); + int marks = pdoc->GetMark(lineDoc); + if (!firstSubLine) + marks = 0; + int levelNum = level & SC_FOLDLEVELNUMBERMASK; + int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; + if (level & SC_FOLDLEVELHEADERFLAG) { + if (firstSubLine) { + 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 { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } + needWhiteClosure = false; + } else if (level & SC_FOLDLEVELWHITEFLAG) { + if (needWhiteClosure) { + if (levelNext & SC_FOLDLEVELWHITEFLAG) { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } else if (levelNum > 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 (levelNextNum > SC_FOLDLEVELBASE) { + marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; + } else { + marks |= 1 << SC_MARKNUM_FOLDERTAIL; + } + } 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) { + char number[100]; + number[0] = '\0'; + if (firstSubLine) + 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 + int width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, istrlen(number)); + int xpos = rcNumber.right - width - 3; + rcNumber.left = xpos; + surface->DrawTextNoClip(rcNumber, vs.styles[STYLE_LINENUMBER].font, + rcNumber.top + vs.maxAscent, number, istrlen(number), + vs.styles[STYLE_LINENUMBER].fore.allocated, + vs.styles[STYLE_LINENUMBER].back.allocated); + } + + if (marks) { + for (int markBit = 0; (markBit < 32) && marks; markBit++) { + if (marks & 1) { + vs.markers[markBit].Draw(surface, rcMarker, vs.styles[STYLE_LINENUMBER].font); + } + marks >>= 1; + } + } + + visibleLine++; + yposScreen += vs.lineHeight; + } + } + } + + PRectangle rcBlankMargin = rcMargin; + rcBlankMargin.left = rcSelMargin.right; + surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back.allocated); + + if (bufferedDraw) { + surfWindow->Copy(rcMargin, Point(), *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); +} + +static bool IsSpaceOrTab(char ch) { + return ch == ' ' || ch == '\t'; +} + +LineLayout *Editor::RetrieveLineLayout(int lineNumber) { + int posLineStart = pdoc->LineStart(lineNumber); + int posLineEnd = pdoc->LineStart(lineNumber + 1); + int lineCaret = pdoc->LineFromPosition(currentPos); + 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) { + if (!ll) + return; + PLATFORM_ASSERT(line < pdoc->LinesTotal()); + 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) { + int cid = posLineEnd - 1; + while ((cid > posLineStart) && IsEOLChar(pdoc->CharAt(cid))) { + cid--; + lineLength--; + } + } + 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<unsigned char>(styleByte & styleMask)); + allSame = allSame && + (ll->indicators[numCharsInLine] == static_cast<char>(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<char>(tolower(chDoc))); + else // Style::caseUpper + allSame = allSame && + (ll->chars[numCharsInLine] == static_cast<char>(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; + int numCharsInLine = 0; + if (vstyle.edgeState == EDGE_BACKGROUND) { + ll->edgeColumn = pdoc->FindColumn(line, theEdge); + if (ll->edgeColumn >= posLineStart) { + ll->edgeColumn -= posLineStart; + } + } else { + ll->edgeColumn = -1; + } + + char styleByte = 0; + int styleMask = pdoc->stylingBitsMask; + ll->styleBitsSet = 0; + // Fill base line layout + for (int charInDoc = posLineStart; charInDoc < posLineEnd; charInDoc++) { + char chDoc = pdoc->CharAt(charInDoc); + styleByte = pdoc->StyleAt(charInDoc); + ll->styleBitsSet |= styleByte; + if (vstyle.viewEOL || (!IsEOLChar(chDoc))) { + ll->chars[numCharsInLine] = chDoc; + ll->styles[numCharsInLine] = static_cast<char>(styleByte & styleMask); + ll->indicators[numCharsInLine] = static_cast<char>(styleByte & ~styleMask); + if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseUpper) + ll->chars[numCharsInLine] = static_cast<char>(toupper(chDoc)); + else if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseLower) + ll->chars[numCharsInLine] = static_cast<char>(tolower(chDoc)); + numCharsInLine++; + } + } + 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. + int startseg = 0; // Start of the current segment, in char. number + int startsegx = 0; // Start of the current segment, in pixels + ll->positions[0] = 0; + unsigned int tabWidth = vstyle.spaceWidth * pdoc->tabInChars; + bool lastSegItalics = false; + Font &ctrlCharsFont = vstyle.styles[STYLE_CONTROLCHAR].font; + + int ctrlCharWidth[32] = {0}; + bool isControlNext = IsControlCharacter(ll->chars[0]); + for (int charInLine = 0; charInLine < numCharsInLine; charInLine++) { + bool isControl = isControlNext; + isControlNext = IsControlCharacter(ll->chars[charInLine + 1]); + if ((ll->styles[charInLine] != ll->styles[charInLine + 1]) || + isControl || isControlNext) { + ll->positions[startseg] = 0; + if (vstyle.styles[ll->styles[charInLine]].visible) { + if (isControl) { + if (ll->chars[charInLine] == '\t') { + ll->positions[charInLine + 1] = ((((startsegx + 2) / + tabWidth) + 1) * tabWidth) - startsegx; + } else if (controlCharSymbol < 32) { + if (ctrlCharWidth[ll->chars[charInLine]] == 0) { + const char *ctrlChar = ControlCharacterString(ll->chars[charInLine]); + // +3 For a blank on front and rounded edge each side: + ctrlCharWidth[ll->chars[charInLine]] = + surface->WidthText(ctrlCharsFont, ctrlChar, istrlen(ctrlChar)) + 3; + } + ll->positions[charInLine + 1] = ctrlCharWidth[ll->chars[charInLine]]; + } else { + char cc[2] = { static_cast<char>(controlCharSymbol), '\0' }; + surface->MeasureWidths(ctrlCharsFont, cc, 1, + ll->positions + startseg + 1); + } + lastSegItalics = false; + } else { // Regular character + int lenSeg = charInLine - startseg + 1; + if ((lenSeg == 1) && (' ' == ll->chars[startseg])) { + lastSegItalics = false; + // Over half the segments are single characters and of these about half are space characters. + ll->positions[charInLine + 1] = vstyle.styles[ll->styles[charInLine]].spaceWidth; + } else { + lastSegItalics = vstyle.styles[ll->styles[charInLine]].italic; + surface->MeasureWidths(vstyle.styles[ll->styles[charInLine]].font, ll->chars + startseg, + lenSeg, ll->positions + startseg + 1); + } + } + } else { // invisible + for (int posToZero = startseg; posToZero <= (charInLine + 1); posToZero++) { + ll->positions[posToZero] = 0; + } + } + for (int posToIncrease = startseg; posToIncrease <= (charInLine + 1); posToIncrease++) { + ll->positions[posToIncrease] += startsegx; + } + startsegx = ll->positions[charInLine + 1]; + startseg = charInLine + 1; + } + } + // Small hack to make lines that end with italics not cut off the edge of the last character + if ((startseg > 0) && lastSegItalics) { + ll->positions[startseg] += 2; + } + ll->numCharsInLine = numCharsInLine; + 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 (wrapVisualFlags & SC_WRAPVISUALFLAG_END) { + width -= vstyle.aveCharWidth; // take into account the space for end wrap mark + } + ll->lines = 0; + // Calculate line start positions based upon width. + // For now this is simplistic - wraps on byte rather than character and + // in the middle of words. Should search for spaces or style changes. + int lastGoodBreak = 0; + int lastLineStart = 0; + int 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 -= actualWrapVisualStartIndent * vstyle.aveCharWidth; + p = lastGoodBreak + 1; + continue; + } + if (p > 0) { + if (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; + } +} + +ColourAllocated Editor::SelectionBackground(ViewStyle &vsDraw) { + return primarySelection ? vsDraw.selbackground.allocated : vsDraw.selbackground2.allocated; +} + +ColourAllocated Editor::TextBackground(ViewStyle &vsDraw, bool overrideBackground, + ColourAllocated background, bool inSelection, bool inHotspot, int styleMain, int i, LineLayout *ll) { + if (inSelection) { + if (vsDraw.selbackset && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { + return SelectionBackground(vsDraw); + } + } else { + if ((vsDraw.edgeState == EDGE_BACKGROUND) && + (i >= ll->edgeColumn) && + !IsEOLChar(ll->chars[i])) + return vsDraw.edgecolour.allocated; + if (inHotspot && vsDraw.hotspotBackgroundSet) + return vsDraw.hotspotBackground.allocated; + if (overrideBackground) + return background; + } + return vsDraw.styles[styleMain].back.allocated; +} + +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, ColourAllocated 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 + bool yStraight = true; + //bool yStraight= isEndMarker; // comment in for start marker y-mirrowed + + int x0 = xStraight ? rcPlace.left : rcPlace.right - 1; + int y0 = yStraight ? rcPlace.top : rcPlace.bottom - 1; + + 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, yStraight ? 1 : -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, ColourAllocated fill, int alpha) { + if (alpha != SC_ALPHA_NOALPHA) { + surface->AlphaRectangle(rc, 0, fill, alpha, fill, alpha, 0); + } +} + +void Editor::DrawEOL(Surface *surface, ViewStyle &vsDraw, PRectangle rcLine, LineLayout *ll, + int line, int lineEnd, int xStart, int subLine, int subLineStart, + bool overrideBackground, ColourAllocated background, + bool drawWrapMarkEnd, ColourAllocated wrapColour) { + + int styleMask = pdoc->stylingBitsMask; + PRectangle rcSegment = rcLine; + + // Fill in a PRectangle representing the end of line characters + int xEol = ll->positions[lineEnd] - subLineStart; + rcSegment.left = xEol + xStart; + rcSegment.right = xEol + vsDraw.aveCharWidth + xStart; + int posLineEnd = pdoc->LineStart(line + 1); + bool eolInSelection = (subLine == (ll->lines - 1)) && + (posLineEnd > ll->selStart) && (posLineEnd <= ll->selEnd) && (ll->selStart != ll->selEnd); + + if (eolInSelection && vsDraw.selbackset && (line < pdoc->LinesTotal() - 1) && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { + surface->FillRectangle(rcSegment, SelectionBackground(vsDraw)); + } else { + if (overrideBackground) { + surface->FillRectangle(rcSegment, background); + } else { + surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back.allocated); + } + if (eolInSelection && vsDraw.selbackset && (line < pdoc->LinesTotal() - 1) && (vsDraw.selAlpha != SC_ALPHA_NOALPHA)) { + SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw), vsDraw.selAlpha); + } + } + + rcSegment.left = xEol + vsDraw.aveCharWidth + xStart; + rcSegment.right = rcLine.right; + 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.allocated); + } else { + surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back.allocated); + } + + if (drawWrapMarkEnd) { + PRectangle rcPlace = rcSegment; + + if (wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_END_BY_TEXT) { + rcPlace.left = xEol + xStart; + rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; + } else { + // draw left of the right text margin, to avoid clipping by the current clip rect + rcPlace.right = rcLine.right - vs.rightMarginWidth; + rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; + } + DrawWrapMarker(surface, rcPlace, true, wrapColour); + } +} + +void Editor::DrawLine(Surface *surface, ViewStyle &vsDraw, int line, int lineVisible, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine) { + + 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; + ColourAllocated background; + if (caret.active && vsDraw.showCaretLineBackground && (vsDraw.caretLineAlpha == SC_ALPHA_NOALPHA) && ll->containsCaret) { + overrideBackground = true; + background = vsDraw.caretLineBackground.allocated; + } + 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.allocated; + 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.allocated; + } + marksMasked >>= 1; + } + } + } + } + + bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && + (!overrideBackground) && (vsDraw.whitespaceBackgroundSet); + + bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. + int indentWidth = pdoc->IndentSize() * vsDraw.spaceWidth; + + int posLineStart = pdoc->LineStart(line); + + int startseg = ll->LineStart(subLine); + int subLineStart = ll->positions[startseg]; + int lineStart = 0; + int lineEnd = 0; + if (subLine < ll->lines) { + lineStart = ll->LineStart(subLine); + lineEnd = ll->LineStart(subLine + 1); + } + + bool drawWrapMarkEnd = false; + + if (wrapVisualFlags & SC_WRAPVISUALFLAG_END) { + if (subLine + 1 < ll->lines) { + drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; + } + } + + if (actualWrapVisualStartIndent != 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 + actualWrapVisualStartIndent * vsDraw.aveCharWidth; + + // default bgnd here.. + surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back.allocated); + + // 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.allocated); + + if (wrapVisualFlags & SC_WRAPVISUALFLAG_START) { + + if (wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_START_BY_TEXT) + rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; + else + rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; + + DrawWrapMarker(surface, rcPlace, false, vsDraw.whitespaceForeground.allocated); + } + + xStart += actualWrapVisualStartIndent * vsDraw.aveCharWidth; + } + } + + int i; + + // Background drawing loop + for (i = lineStart; twoPhaseDraw && (i < lineEnd); i++) { + + int iDoc = i + posLineStart; + // If there is the end of a style run for any reason + if ((ll->styles[i] != ll->styles[i + 1]) || + i == (lineEnd - 1) || + IsControlCharacter(ll->chars[i]) || IsControlCharacter(ll->chars[i + 1]) || + ((ll->selStart != ll->selEnd) && ((iDoc + 1 == ll->selStart) || (iDoc + 1 == ll->selEnd))) || + (i == (ll->edgeColumn - 1))) { + rcSegment.left = ll->positions[startseg] + xStart - subLineStart; + rcSegment.right = ll->positions[i + 1] + 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.left <= rcLine.right) && (rcSegment.right >= rcLine.left)) { + int styleMain = ll->styles[i]; + bool inSelection = (iDoc >= ll->selStart) && (iDoc < ll->selEnd) && (ll->selStart != ll->selEnd); + bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); + ColourAllocated textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, styleMain, i, ll); + if (ll->chars[i] == '\t') { + // Tab display + if (drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) + textBack = vsDraw.whitespaceBackground.allocated; + surface->FillRectangle(rcSegment, textBack); + } else if (IsControlCharacter(ll->chars[i])) { + // Control character display + inIndentation = false; + surface->FillRectangle(rcSegment, textBack); + } else { + // Normal text display + surface->FillRectangle(rcSegment, textBack); + if (vsDraw.viewWhitespace != wsInvisible || + (inIndentation && vsDraw.viewIndentationGuides)) { + for (int cpos = 0; cpos <= i - startseg; cpos++) { + if (ll->chars[cpos + startseg] == ' ') { + if (drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { + PRectangle rcSpace(ll->positions[cpos + startseg] + xStart, rcSegment.top, + ll->positions[cpos + startseg + 1] + xStart, rcSegment.bottom); + surface->FillRectangle(rcSpace, vsDraw.whitespaceBackground.allocated); + } + } else { + inIndentation = false; + } + } + } + } + } else if (rcSegment.left > rcLine.right) { + break; + } + startseg = i + 1; + } + } + + if (twoPhaseDraw) { + DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, + xStart, subLine, subLineStart, overrideBackground, background, + drawWrapMarkEnd, vsDraw.whitespaceForeground.allocated); + } + + inIndentation = subLine == 0; // Do not handle indentation except on first subline. + startseg = ll->LineStart(subLine); + // Foreground drawing loop + for (i = lineStart; i < lineEnd; i++) { + + int iDoc = i + posLineStart; + // If there is the end of a style run for any reason + if ((ll->styles[i] != ll->styles[i + 1]) || + i == (lineEnd - 1) || + IsControlCharacter(ll->chars[i]) || IsControlCharacter(ll->chars[i + 1]) || + ((ll->selStart != ll->selEnd) && ((iDoc + 1 == ll->selStart) || (iDoc + 1 == ll->selEnd))) || + (i == (ll->edgeColumn - 1))) { + rcSegment.left = ll->positions[startseg] + xStart - subLineStart; + rcSegment.right = ll->positions[i + 1] + 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.left <= rcLine.right) && (rcSegment.right >= rcLine.left)) { + int styleMain = ll->styles[i]; + ColourAllocated textFore = vsDraw.styles[styleMain].fore.allocated; + Font &textFont = vsDraw.styles[styleMain].font; + //hotspot foreground + if (ll->hsStart != -1 && iDoc >= ll->hsStart && iDoc < hsEnd) { + if (vsDraw.hotspotForegroundSet) + textFore = vsDraw.hotspotForeground.allocated; + } + bool inSelection = (iDoc >= ll->selStart) && (iDoc < ll->selEnd) && (ll->selStart != ll->selEnd); + if (inSelection && (vsDraw.selforeset)) { + textFore = vsDraw.selforeground.allocated; + } + bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); + ColourAllocated textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, styleMain, i, ll); + if (ll->chars[i] == '\t') { + // Tab display + if (!twoPhaseDraw) { + if (drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) + textBack = vsDraw.whitespaceBackground.allocated; + surface->FillRectangle(rcSegment, textBack); + } + if ((vsDraw.viewWhitespace != wsInvisible) || ((inIndentation && vsDraw.viewIndentationGuides))) { + if (vsDraw.whitespaceForegroundSet) + textFore = vsDraw.whitespaceForeground.allocated; + surface->PenColour(textFore); + } + if (inIndentation && vsDraw.viewIndentationGuides) { + for (int xIG = ll->positions[i] / indentWidth * indentWidth; xIG < ll->positions[i + 1]; xIG += indentWidth) { + if (xIG >= ll->positions[i] && xIG > 0) { + DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIG + xStart, rcSegment, + (ll->xHighlightGuide == xIG)); + } + } + } + if (vsDraw.viewWhitespace != wsInvisible) { + if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { + PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, + rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); + DrawTabArrow(surface, rcTab, rcSegment.top + vsDraw.lineHeight / 2); + } + } + } else if (IsControlCharacter(ll->chars[i])) { + // Control character display + inIndentation = false; + if (controlCharSymbol < 32) { + // Draw the character + const char *ctrlChar = ControlCharacterString(ll->chars[i]); + if (!twoPhaseDraw) { + surface->FillRectangle(rcSegment, textBack); + } + 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, ctrlChar, istrlen(ctrlChar), + textBack, textFore); + } else { + char cc[2] = { static_cast<char>(controlCharSymbol), '\0' }; + surface->DrawTextNoClip(rcSegment, ctrlCharsFont, + rcSegment.top + vsDraw.maxAscent, + cc, 1, textBack, textFore); + } + } else { + // Normal text display + if (vsDraw.styles[styleMain].visible) { + if (twoPhaseDraw) { + surface->DrawTextTransparent(rcSegment, textFont, + rcSegment.top + vsDraw.maxAscent, ll->chars + startseg, + i - startseg + 1, textFore); + } else { + surface->DrawTextNoClip(rcSegment, textFont, + rcSegment.top + vsDraw.maxAscent, ll->chars + startseg, + i - startseg + 1, textFore, textBack); + } + } + if (vsDraw.viewWhitespace != wsInvisible || + (inIndentation && vsDraw.viewIndentationGuides)) { + for (int cpos = 0; cpos <= i - startseg; cpos++) { + if (ll->chars[cpos + startseg] == ' ') { + if (vsDraw.viewWhitespace != wsInvisible) { + if (vsDraw.whitespaceForegroundSet) + textFore = vsDraw.whitespaceForeground.allocated; + if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { + int xmid = (ll->positions[cpos + startseg] + ll->positions[cpos + startseg + 1]) / 2; + if (!twoPhaseDraw && drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { + textBack = vsDraw.whitespaceBackground.allocated; + PRectangle rcSpace(ll->positions[cpos + startseg] + xStart, rcSegment.top, ll->positions[cpos + startseg + 1] + xStart, rcSegment.bottom); + surface->FillRectangle(rcSpace, textBack); + } + PRectangle rcDot(xmid + xStart - subLineStart, rcSegment.top + vsDraw.lineHeight / 2, 0, 0); + rcDot.right = rcDot.left + 1; + rcDot.bottom = rcDot.top + 1; + surface->FillRectangle(rcDot, textFore); + } + } + if (inIndentation && vsDraw.viewIndentationGuides) { + int startSpace = ll->positions[cpos + startseg]; + if (startSpace > 0 && (startSpace % indentWidth == 0)) { + DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, startSpace + xStart, rcSegment, + (ll->xHighlightGuide == ll->positions[cpos + startseg])); + } + } + } 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.hotspotForegroundSet) + surface->FillRectangle(rcUL, vsDraw.hotspotForeground.allocated); + 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; + } + startseg = i + 1; + } + } + + // 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! + PRectangle rcIndic( + ll->positions[startPos] + xStart - subLineStart, + rcLine.top + vsDraw.maxAscent, + ll->positions[indicPos] + xStart - subLineStart, + rcLine.top + vsDraw.maxAscent + 3); + vsDraw.indicators[indicnum].Draw(surface, rcIndic, rcLine); + // RESET control var + startPos = -1; + } + } + } + mask <<= 1; + } + // End of the drawing of the current line + if (!twoPhaseDraw) { + DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, + xStart, subLine, subLineStart, overrideBackground, background, + drawWrapMarkEnd, vsDraw.whitespaceForeground.allocated); + } + if ((vsDraw.selAlpha != SC_ALPHA_NOALPHA) && (ll->selStart >= 0) && (ll->selEnd >= 0)) { + int startPosSel = (ll->selStart < posLineStart) ? posLineStart : ll->selStart; + int endPosSel = (ll->selEnd < (lineEnd + posLineStart)) ? ll->selEnd : (lineEnd + posLineStart); + if (startPosSel < endPosSel) { + rcSegment.left = xStart + ll->positions[startPosSel - posLineStart] - subLineStart; + rcSegment.right = xStart + ll->positions[endPosSel - posLineStart] - subLineStart; + SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw), vsDraw.selAlpha); + } + } + + if (vsDraw.edgeState == EDGE_LINE) { + int edgeX = theEdge * vsDraw.spaceWidth; + rcSegment.left = edgeX + xStart; + rcSegment.right = rcSegment.left + 1; + surface->FillRectangle(rcSegment, vsDraw.edgecolour.allocated); + } + + // Draw any translucent whole line states + rcSegment.left = xStart; + rcSegment.right = rcLine.right - 1; + if (caret.active && vsDraw.showCaretLineBackground && ll->containsCaret) { + SimpleAlphaRectangle(surface, rcSegment, vsDraw.caretLineBackground.allocated, vsDraw.caretLineAlpha); + } + int marks = pdoc->GetMark(line); + for (int markBit = 0; (markBit < 32) && marks; markBit++) { + if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND)) { + SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back.allocated, vsDraw.markers[markBit].alpha); + } + marks >>= 1; + } + 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)) { + SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back.allocated, vsDraw.markers[markBit].alpha); + } + marksMasked >>= 1; + } + } + } +} + +void Editor::RefreshPixMaps(Surface *surfaceWindow) { + if (!pixmapSelPattern->Initialised()) { + const int patternSize = 8; + pixmapSelPattern->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. + ColourAllocated colourFMFill = vs.selbar.allocated; + ColourAllocated colourFMStripes = vs.selbarlight.allocated; + + if (!(vs.selbarlight.desired == 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.allocated; + } + + if (vs.foldmarginColourSet) { + // override default fold margin colour + colourFMFill = vs.foldmarginColour.allocated; + } + if (vs.foldmarginHighlightColourSet) { + // override default fold margin highlight colour + colourFMStripes = vs.foldmarginHighlightColour.allocated; + } + + pixmapSelPattern->FillRectangle(rcPattern, colourFMFill); + pixmapSelPattern->PenColour(colourFMStripes); + for (int stripe = 0; stripe < patternSize; stripe++) { + // Alternating 1 pixel stripes is same as checkerboard. + pixmapSelPattern->MoveTo(0, stripe * 2); + pixmapSelPattern->LineTo(patternSize, stripe * 2 - patternSize); + } + } + + 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.allocated); + pixmapIndentGuide->PenColour(vs.styles[STYLE_INDENTGUIDE].fore.allocated); + pixmapIndentGuideHighlight->FillRectangle(rcIG, vs.styles[STYLE_BRACELIGHT].back.allocated); + pixmapIndentGuideHighlight->PenColour(vs.styles[STYLE_BRACELIGHT].fore.allocated); + for (int stripe = 1; stripe < vs.lineHeight + 1; stripe += 2) { + pixmapIndentGuide->MoveTo(0, stripe); + pixmapIndentGuide->LineTo(2, stripe); + pixmapIndentGuideHighlight->MoveTo(0, stripe); + pixmapIndentGuideHighlight->LineTo(2, stripe); + } + } + + 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::Paint(Surface *surfaceWindow, PRectangle rcArea) { + //Platform::DebugPrintf("Paint:%1d (%3d,%3d) ... (%3d,%3d)\n", + // paintingAllText, rcArea.left, rcArea.top, rcArea.right, rcArea.bottom); + + RefreshStyleData(); + RefreshPixMaps(surfaceWindow); + + PRectangle rcClient = GetClientRectangle(); + //Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", + // rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); + + surfaceWindow->SetPalette(&palette, true); + pixmapLine->SetPalette(&palette, !hasFocus); + + int screenLinePaintFirst = rcArea.top / vs.lineHeight; + // The area to be painted plus one extra line is styled. + // The extra line is to determine when a style change, such as starting a comment flows on to other lines. + int lineStyleLast = topLine + (rcArea.bottom - 1) / vs.lineHeight + 1; + //Platform::DebugPrintf("Paint lines = %d .. %d\n", topLine + screenLinePaintFirst, lineStyleLast); + int endPosPaint = pdoc->Length(); + if (lineStyleLast < cs.LinesDisplayed()) + endPosPaint = pdoc->LineStart(cs.DocFromDisplay(lineStyleLast + 1)); + + int xStart = vs.fixedColumnWidth - xOffset; + int ypos = 0; + if (!bufferedDraw) + ypos += screenLinePaintFirst * vs.lineHeight; + int yposScreen = screenLinePaintFirst * vs.lineHeight; + + // Ensure we are styled as far as we are painting. + pdoc->EnsureStyledTo(endPosPaint); + bool paintAbandonedByStyling = paintState == paintAbandoned; + if (needUpdateUI) { + NotifyUpdateUI(); + needUpdateUI = false; + RefreshStyleData(); + RefreshPixMaps(surfaceWindow); + } + + // Call priority lines wrap on a window of lines which are likely + // to rendered with the following paint (that is wrap the visible + // lines first). + int startLineToWrap = cs.DocFromDisplay(topLine) - 5; + if (startLineToWrap < 0) + startLineToWrap = -1; + if (WrapLines(false, startLineToWrap)) { + // 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()); + + PaintSelMargin(surfaceWindow, rcArea); + + PRectangle rcRightMargin = rcClient; + rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; + if (rcArea.Intersects(rcRightMargin)) { + surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back.allocated); + } + + if (paintState == paintAbandoned) { + // Either styling or NotifyUpdateUI noticed that painting is needed + // outside the current painting rectangle + //Platform::DebugPrintf("Abandoning paint\n"); + if (wrapState != eWrapNone) { + 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); + + // Do the painting + if (rcArea.right > vs.fixedColumnWidth) { + + Surface *surface = surfaceWindow; + if (bufferedDraw) { + surface = pixmapLine; + PLATFORM_ASSERT(pixmapLine->Initialised()); + } + surface->SetUnicodeMode(IsUnicodeMode()); + surface->SetDBCSMode(CodePage()); + + int visibleLine = topLine + screenLinePaintFirst; + + int posCaret = currentPos; + if (posDrag >= 0) + posCaret = posDrag; + int lineCaret = pdoc->LineFromPosition(posCaret); + + // Remove selection margin from drawing area so text will not be drawn + // on it in unbuffered mode. + PRectangle rcTextArea = rcClient; + rcTextArea.left = vs.fixedColumnWidth; + rcTextArea.right -= vs.rightMarginWidth; + surfaceWindow->SetClip(rcTextArea); + + // 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); + SelectionLineIterator lineIterator(this); + 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); + // For rectangular selection this accesses the layout cache so should be after layout returned. + lineIterator.SetAt(lineDoc); + ll.Set(RetrieveLineLayout(lineDoc)); + LayoutLine(lineDoc, surface, vs, ll, wrapWidth); + lineDocPrevious = lineDoc; + } + //durLayout += et.Duration(true); + + if (ll) { + if (selType == selStream) { + ll->selStart = SelectionStart(); + ll->selEnd = SelectionEnd(); + } else { + ll->selStart = lineIterator.startPos; + ll->selEnd = lineIterator.endPos; + } + ll->containsCaret = lineDoc == lineCaret; + if (hideSelection) { + ll->selStart = -1; + ll->selEnd = -1; + ll->containsCaret = false; + } + + GetHotSpotRange(ll->hsStart, ll->hsEnd); + + PRectangle rcLine = rcClient; + rcLine.top = ypos; + rcLine.bottom = ypos + vs.lineHeight; + + Range rangeLine(pdoc->LineStart(lineDoc), pdoc->LineStart(lineDoc + 1)); + // Highlight the current braces if any + ll->SetBracesHighlight(rangeLine, braces, static_cast<char>(bracesMatchStyle), + highlightGuideColumn * vs.spaceWidth); + + // 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); + + bool expanded = cs.GetExpanded(lineDoc); + if ((foldFlags & SC_FOLDFLAG_BOX) == 0) { + // Paint the line above the fold + if ((expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_EXPANDED)) + || + (!expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_CONTRACTED))) { + if (pdoc->GetLevel(lineDoc) & SC_FOLDLEVELHEADERFLAG) { + PRectangle rcFoldLine = rcLine; + rcFoldLine.bottom = rcFoldLine.top + 1; + surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); + } + } + // Paint the line below the fold + if ((expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_EXPANDED)) + || + (!expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { + if (pdoc->GetLevel(lineDoc) & SC_FOLDLEVELHEADERFLAG) { + PRectangle rcFoldLine = rcLine; + rcFoldLine.top = rcFoldLine.bottom - 1; + surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); + } + } + } else { + int FoldLevelCurr = (pdoc->GetLevel(lineDoc) & SC_FOLDLEVELNUMBERMASK) - SC_FOLDLEVELBASE; + int FoldLevelPrev = (pdoc->GetLevel(lineDoc - 1) & SC_FOLDLEVELNUMBERMASK) - SC_FOLDLEVELBASE; + int FoldLevelFlags = (pdoc->GetLevel(lineDoc) & ~SC_FOLDLEVELNUMBERMASK) & ~(0xFFF0000); + int indentationStep = pdoc->IndentSize(); + // Draw line above fold + if ((FoldLevelPrev < FoldLevelCurr) + || + (FoldLevelFlags & SC_FOLDLEVELBOXHEADERFLAG + && + (pdoc->GetLevel(lineDoc - 1) & SC_FOLDLEVELBOXFOOTERFLAG) == 0)) { + PRectangle rcFoldLine = rcLine; + rcFoldLine.bottom = rcFoldLine.top + 1; + rcFoldLine.left += xStart + FoldLevelCurr * vs.spaceWidth * indentationStep - 1; + surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); + } + + // Line below the fold (or below a contracted fold) + if (FoldLevelFlags & SC_FOLDLEVELBOXFOOTERFLAG + || + (!expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { + PRectangle rcFoldLine = rcLine; + rcFoldLine.top = rcFoldLine.bottom - 1; + rcFoldLine.left += xStart + (FoldLevelCurr) * vs.spaceWidth * indentationStep - 1; + surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); + } + + PRectangle rcBoxLine = rcLine; + // Draw vertical line for every fold level + for (int i = 0; i <= FoldLevelCurr; i++) { + rcBoxLine.left = xStart + i * vs.spaceWidth * indentationStep - 1; + rcBoxLine.right = rcBoxLine.left + 1; + surface->FillRectangle(rcBoxLine, vs.styles[STYLE_DEFAULT].fore.allocated); + } + } + + // Draw the Caret + if (lineDoc == lineCaret) { + int offset = Platform::Minimum(posCaret - rangeLine.start, ll->maxLineLength); + if ((offset >= ll->LineStart(subLine)) && + ((offset < ll->LineStart(subLine + 1)) || offset == ll->numCharsInLine)) { + int xposCaret = ll->positions[offset] - ll->positions[ll->LineStart(subLine)] + xStart; + + if (actualWrapVisualStartIndent != 0) { + int lineStart = ll->LineStart(subLine); + if (lineStart != 0) // Wrapped + xposCaret += actualWrapVisualStartIndent * vs.aveCharWidth; + } + int widthOverstrikeCaret; + if (posCaret == pdoc->Length()) { // At end of document + widthOverstrikeCaret = vs.aveCharWidth; + } else if ((posCaret - rangeLine.start) >= ll->numCharsInLine) { // At end of line + widthOverstrikeCaret = vs.aveCharWidth; + } else { + widthOverstrikeCaret = ll->positions[offset + 1] - ll->positions[offset]; + } + if (widthOverstrikeCaret < 3) // Make sure its visible + widthOverstrikeCaret = 3; + if (((caret.active && caret.on) || (posDrag >= 0)) && xposCaret >= 0) { + PRectangle rcCaret = rcLine; + int caretWidthOffset = 0; + if ((offset > 0) && (vs.caretWidth > 1)) + caretWidthOffset = 1; // Move back so overlaps both character cells. + if (posDrag >= 0) { + rcCaret.left = xposCaret - caretWidthOffset; + rcCaret.right = rcCaret.left + vs.caretWidth; + } else { + if (inOverstrike) { + rcCaret.top = rcCaret.bottom - 2; + rcCaret.left = xposCaret + 1; + rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; + } else { + rcCaret.left = xposCaret - caretWidthOffset; + rcCaret.right = rcCaret.left + vs.caretWidth; + } + } + surface->FillRectangle(rcCaret, vs.caretcolour.allocated); + } + } + } + + if (bufferedDraw) { + Point from(vs.fixedColumnWidth, 0); + PRectangle rcCopyArea(vs.fixedColumnWidth, yposScreen, + rcClient.right, yposScreen + vs.lineHeight); + surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); + } + //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 = rcClient; + rcBeyondEOF.left = vs.fixedColumnWidth; + rcBeyondEOF.right = rcBeyondEOF.right; + rcBeyondEOF.top = (cs.LinesDisplayed() - topLine) * vs.lineHeight; + if (rcBeyondEOF.top < rcBeyondEOF.bottom) { + surfaceWindow->FillRectangle(rcBeyondEOF, vs.styles[STYLE_DEFAULT].back.allocated); + if (vs.edgeState == EDGE_LINE) { + int edgeX = theEdge * vs.spaceWidth; + rcBeyondEOF.left = edgeX + xStart; + rcBeyondEOF.right = rcBeyondEOF.left + 1; + surfaceWindow->FillRectangle(rcBeyondEOF, vs.edgecolour.allocated); + } + } + //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, RangeToFormat *pfr) { + if (!pfr) + return 0; + + AutoSurface surface(pfr->hdc, this); + if (!surface) + return 0; + AutoSurface surfaceMeasure(pfr->hdcTarget, this); + if (!surfaceMeasure) { + return 0; + } + + ViewStyle vsPrint(vs); + + // 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 < ViewStyle::margins; margin++) { + if ((vsPrint.ms[margin].style == SC_MARGIN_NUMBER) && (vsPrint.ms[margin].width > 0)) { + lineNumberIndex = margin; + } else { + vsPrint.ms[margin].width = 0; + } + } + vsPrint.showMarkedLines = false; + vsPrint.fixedColumnWidth = 0; + vsPrint.zoomLevel = printMagnification; + vsPrint.viewIndentationGuides = false; + // Don't show the selection when printing + vsPrint.selbackset = false; + vsPrint.selforeset = false; + vsPrint.selAlpha = SC_ALPHA_NOALPHA; + vsPrint.whitespaceBackgroundSet = false; + vsPrint.whitespaceForegroundSet = false; + vsPrint.showCaretLineBackground = false; + + // Set colours for printing according to users settings + for (int sty = 0;sty <= STYLE_MAX;sty++) { + if (printColourMode == SC_PRINT_INVERTLIGHT) { + vsPrint.styles[sty].fore.desired = InvertedLight(vsPrint.styles[sty].fore.desired); + vsPrint.styles[sty].back.desired = InvertedLight(vsPrint.styles[sty].back.desired); + } else if (printColourMode == SC_PRINT_BLACKONWHITE) { + vsPrint.styles[sty].fore.desired = ColourDesired(0, 0, 0); + vsPrint.styles[sty].back.desired = ColourDesired(0xff, 0xff, 0xff); + } else if (printColourMode == SC_PRINT_COLOURONWHITE) { + vsPrint.styles[sty].back.desired = ColourDesired(0xff, 0xff, 0xff); + } else if (printColourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { + if (sty <= STYLE_DEFAULT) { + vsPrint.styles[sty].back.desired = ColourDesired(0xff, 0xff, 0xff); + } + } + } + // White background for the line numbers + vsPrint.styles[STYLE_LINENUMBER].back.desired = ColourDesired(0xff, 0xff, 0xff); + + vsPrint.Refresh(*surfaceMeasure); + // Ensure colours are set up + vsPrint.RefreshColourPalette(palette, true); + vsPrint.RefreshColourPalette(palette, false); + // 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; + } + + 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 + lineNumberWidth; + int ypos = pfr->rc.top; + + int lineDoc = linePrintStart; + + int nPrintPos = pfr->chrg.cpMin; + int visibleLine = 0; + int widthPrint = pfr->rc.Width() - lineNumberWidth; + if (printWrapState == 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(8000); + LayoutLine(lineDoc, surfaceMeasure, vsPrint, &ll, widthPrint); + + ll.selStart = -1; + ll.selEnd = -1; + ll.containsCaret = false; + + PRectangle rcLine; + rcLine.left = pfr->rc.left + lineNumberWidth; + 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 -= 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.allocated, + vsPrint.styles[STYLE_LINENUMBER].back.allocated); + } + + // 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; + } + + 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(); + SetScrollBars(); + if (wrapState != eWrapNone) { + PRectangle rcTextArea = GetClientRectangle(); + rcTextArea.left = vs.fixedColumnWidth; + rcTextArea.right -= vs.rightMarginWidth; + if (wrapWidth != rcTextArea.Width()) { + NeedWrapping(); + Redraw(); + } + } +} + +void Editor::AddChar(char ch) { + char s[2]; + s[0] = ch; + s[1] = '\0'; + AddCharUTF(s, 1); +} + +void Editor::AddCharUTF(char *s, unsigned int len, bool treatAsDBCS) { + bool wasSelection = currentPos != anchor; + ClearSelection(); + bool charReplaceAction = false; + if (inOverstrike && !wasSelection && !RangeContainsProtected(currentPos, currentPos + 1)) { + if (currentPos < (pdoc->Length())) { + if (!IsEOLChar(pdoc->CharAt(currentPos))) { + charReplaceAction = true; + pdoc->BeginUndoAction(); + pdoc->DelChar(currentPos); + } + } + } + if (pdoc->InsertString(currentPos, s, len)) { + SetEmptySelection(currentPos + len); + } + if (charReplaceAction) { + pdoc->EndUndoAction(); + } + EnsureCaretVisible(); + // Avoid blinking during rapid typing: + ShowCaretAtCurrentPosition(); + if (!caretSticky) { + SetLastXChosen(); + } + + if (treatAsDBCS) { + NotifyChar((static_cast<unsigned char>(s[0]) << 8) | + static_cast<unsigned char>(s[1])); + } else { + int byte = static_cast<unsigned char>(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<unsigned char>(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<unsigned char>(s[1]); + int byte3 = static_cast<unsigned char>(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); + } +} + +void Editor::ClearSelection() { + if (!SelectionContainsProtected()) { + int startPos = SelectionStart(); + if (selType == selStream) { + unsigned int chars = SelectionEnd() - startPos; + if (0 != chars) { + pdoc->BeginUndoAction(); + pdoc->DeleteChars(startPos, chars); + pdoc->EndUndoAction(); + } + } else { + pdoc->BeginUndoAction(); + SelectionLineIterator lineIterator(this, false); + while (lineIterator.Iterate()) { + startPos = lineIterator.startPos; + unsigned int chars = lineIterator.endPos - startPos; + if (0 != chars) { + pdoc->DeleteChars(startPos, chars); + } + } + pdoc->EndUndoAction(); + selType = selStream; + } + SetEmptySelection(startPos); + } +} + +void Editor::ClearAll() { + pdoc->BeginUndoAction(); + if (0 != pdoc->Length()) { + pdoc->DeleteChars(0, pdoc->Length()); + } + if (!pdoc->IsReadOnly()) { + cs.Clear(); + } + pdoc->EndUndoAction(); + anchor = 0; + currentPos = 0; + SetTopLine(0); + SetVerticalScrollPos(); + InvalidateStyleRedraw(); +} + +void Editor::ClearDocumentStyle() { + pdoc->StartStyling(0, '\377'); + pdoc->SetStyleFor(pdoc->Length(), 0); + cs.ShowAll(); + pdoc->ClearLevels(); +} + +void Editor::Cut() { + if (!pdoc->IsReadOnly() && !SelectionContainsProtected()) { + Copy(); + ClearSelection(); + } +} + +void Editor::PasteRectangular(int pos, const char *ptr, int len) { + if (pdoc->IsReadOnly() || SelectionContainsProtected()) { + return; + } + currentPos = pos; + int xInsert = XFromPosition(currentPos); + int line = pdoc->LineFromPosition(currentPos); + bool prevCr = false; + pdoc->BeginUndoAction(); + 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 + currentPos = PositionFromLineX(line, xInsert); + if ((XFromPosition(currentPos) < xInsert) && (i + 1 < len)) { + for (int i = 0; i < xInsert - XFromPosition(currentPos); i++) { + pdoc->InsertChar(currentPos, ' '); + currentPos++; + } + } + prevCr = ptr[i] == '\r'; + } else { + pdoc->InsertString(currentPos, ptr + i, 1); + currentPos++; + prevCr = false; + } + } + pdoc->EndUndoAction(); + SetEmptySelection(pos); +} + +bool Editor::CanPaste() { + return !pdoc->IsReadOnly() && !SelectionContainsProtected(); +} + +void Editor::Clear() { + if (currentPos == anchor) { + if (!RangeContainsProtected(currentPos, currentPos + 1)) { + DelChar(); + } + } else { + ClearSelection(); + } + SetEmptySelection(currentPos); +} + +void Editor::SelectAll() { + 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(currentPos, currentPos + 1)) { + pdoc->DelChar(currentPos); + } + // Avoid blinking during rapid typing: + ShowCaretAtCurrentPosition(); +} + +void Editor::DelCharBack(bool allowLineStartDeletion) { + if (currentPos == anchor) { + if (!RangeContainsProtected(currentPos - 1, currentPos)) { + int lineCurrentPos = pdoc->LineFromPosition(currentPos); + if (allowLineStartDeletion || (pdoc->LineStart(lineCurrentPos) != currentPos)) { + if (pdoc->GetColumn(currentPos) <= pdoc->GetLineIndentation(lineCurrentPos) && + pdoc->GetColumn(currentPos) > 0 && pdoc->backspaceUnindents) { + pdoc->BeginUndoAction(); + 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(pdoc->GetLineIndentPosition(lineCurrentPos)); + pdoc->EndUndoAction(); + } else { + pdoc->DelCharBack(currentPos); + } + } + } + } else { + ClearSelection(); + SetEmptySelection(currentPos); + } + // Avoid blinking during rapid typing: + ShowCaretAtCurrentPosition(); +} + +void Editor::NotifyFocus(bool) {} + +void Editor::NotifyStyleToNeeded(int endStyleNeeded) { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_STYLENEEDED; + scn.position = endStyleNeeded; + NotifyParent(scn); +} + +void Editor::NotifyStyleNeeded(Document*, void *, int endStyleNeeded) { + NotifyStyleToNeeded(endStyleNeeded); +} + +void Editor::NotifyChar(int ch) { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_CHARADDED; + scn.ch = ch; + NotifyParent(scn); + if (recordingMacro) { + char txt[2]; + txt[0] = static_cast<char>(ch); + txt[1] = '\0'; + NotifyMacroRecord(SCI_REPLACESEL, 0, reinterpret_cast<sptr_t>(txt)); + } +} + +void Editor::NotifySavePoint(bool isSavePoint) { + SCNotification scn = {0}; + if (isSavePoint) { + scn.nmhdr.code = SCN_SAVEPOINTREACHED; + } else { + scn.nmhdr.code = SCN_SAVEPOINTLEFT; + } + NotifyParent(scn); +} + +void Editor::NotifyModifyAttempt() { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_MODIFYATTEMPTRO; + NotifyParent(scn); +} + +void Editor::NotifyDoubleClick(Point pt, bool) { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_DOUBLECLICK; + scn.line = LineFromLocation(pt); + scn.position = PositionFromLocationClose(pt); + NotifyParent(scn); +} + +void Editor::NotifyHotSpotDoubleClicked(int position, bool shift, bool ctrl, bool alt) { + SCNotification scn = {0}; + 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 = {0}; + 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::NotifyUpdateUI() { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_UPDATEUI; + NotifyParent(scn); +} + +void Editor::NotifyPainted() { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_PAINTED; + NotifyParent(scn); +} + +bool Editor::NotifyMarginClick(Point pt, bool shift, bool ctrl, bool alt) { + int marginClicked = -1; + int x = 0; + for (int margin = 0; margin < ViewStyle::margins; 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) { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_MARGINCLICK; + scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | + (alt ? SCI_ALT : 0); + scn.position = pdoc->LineStart(LineFromLocation(pt)); + scn.margin = marginClicked; + NotifyParent(scn); + return true; + } else { + return false; + } +} + +void Editor::NotifyNeedShown(int pos, int len) { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_NEEDSHOWN; + scn.position = pos; + scn.length = len; + NotifyParent(scn); +} + +void Editor::NotifyDwelling(Point pt, bool state) { + SCNotification scn = {0}; + scn.nmhdr.code = state ? SCN_DWELLSTART : SCN_DWELLEND; + scn.position = PositionFromLocationClose(pt); + scn.x = pt.x; + scn.y = pt.y; + NotifyParent(scn); +} + +void Editor::NotifyZoom() { + SCNotification scn = {0}; + scn.nmhdr.code = SCN_ZOOM; + NotifyParent(scn); +} + +// Notifications from document +void Editor::NotifyModifyAttempt(Document*, void *) { + //Platform::DebugPrintf("** Modify Attempt\n"); + NotifyModifyAttempt(); +} + +void Editor::NotifyMove(int position) { +#ifdef INCLUDE_DEPRECATED_FEATURES + SCNotification scn = {0}; + scn.nmhdr.code = SCN_POSCHANGED; + scn.position = position; + NotifyParent(scn); +#endif +} + +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); + if (wrapState != eWrapNone) { + int lineDoc = pdoc->LineFromPosition(mh.position); + int lines = Platform::Maximum(0, mh.linesAdded); + NeedWrapping(lineDoc, lineDoc + lines + 1); + } + } +} + +// 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 *) { + needUpdateUI = true; + if (paintState == painting) { + CheckForChangeOutsidePaint(Range(mh.position, mh.position + mh.length)); + } + 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); + } + } + llc.Invalidate(LineLayout::llCheckTextAndStyle); + } else { + // Move selection and brace highlights + if (mh.modificationType & SC_MOD_INSERTTEXT) { + currentPos = MovePositionForInsertion(currentPos, mh.position, mh.length); + anchor = MovePositionForInsertion(anchor, 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) { + currentPos = MovePositionForDeletion(currentPos, mh.position, mh.length); + anchor = MovePositionForDeletion(anchor, mh.position, mh.length); + braces[0] = MovePositionForDeletion(braces[0], mh.position, mh.length); + braces[1] = MovePositionForDeletion(braces[1], mh.position, mh.length); + } + if (cs.LinesDisplayed() < cs.LinesInDoc()) { + // Some lines are hidden so may need shown. + // TODO: check if the modified area is hidden. + if (mh.modificationType & SC_MOD_BEFOREINSERT) { + NotifyNeedShown(mh.position, 0); + } else if (mh.modificationType & SC_MOD_BEFOREDELETE) { + NotifyNeedShown(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.linesAdded > 0) { + cs.InsertLines(lineOfPos, mh.linesAdded); + } else { + cs.DeleteLines(lineOfPos, -mh.linesAdded); + } + } + 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(); + } + } + + //Platform::DebugPrintf("** %x Doc Changed\n", this); + // TODO: could invalidate from mh.startModification to end of screen + //InvalidateRange(mh.position, mh.position + mh.length); + if (paintState == notPainting && !CanDeferToLastStep(mh)) { + Redraw(); + } + } else { + //Platform::DebugPrintf("** %x Line Changed %d .. %d\n", this, + // mh.position, mh.position + mh.length); + if (paintState == notPainting && mh.length && !CanEliminate(mh)) { + InvalidateRange(mh.position, mh.position + mh.length); + } + } + } + + if (mh.linesAdded != 0 && !CanDeferToLastStep(mh)) { + SetScrollBars(); + } + + if (mh.modificationType & SC_MOD_CHANGEMARKER) { + if ((paintState == notPainting) || !PaintContainsMargin()) { + if (mh.modificationType & SC_MOD_CHANGEFOLD) { + // Fold changes can affect the drawing of following lines so redraw whole margin + RedrawSelMargin(); + } else { + RedrawSelMargin(mh.line); + } + } + } + + // 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) == 0) { + // Real modification made to text of document. + NotifyChange(); // Send EN_CHANGE + } + + SCNotification scn = {0}; + 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; + 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_DELWORDLEFT: + case SCI_DELWORDRIGHT: + 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: + 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 = {0}; + scn.nmhdr.code = SCN_MACRORECORD; + scn.message = iMessage; + scn.wParam = wParam; + scn.lParam = lParam; + NotifyParent(scn); +} + +/** + * 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, selTypes sel, bool stuttered) { + int topLineNew, newPos; + + // I consider only the caretYSlop, and ignore the caretYPolicy-- is that a problem? + int currentLine = pdoc->LineFromPosition(currentPos); + int topStutterLine = topLine + caretYSlop; + int bottomStutterLine = topLine + LinesToScroll() - caretYSlop; + + if (stuttered && (direction < 0 && currentLine > topStutterLine)) { + topLineNew = topLine; + newPos = PositionFromLocation(Point(lastXChosen, vs.lineHeight * caretYSlop)); + + } else if (stuttered && (direction > 0 && currentLine < bottomStutterLine)) { + topLineNew = topLine; + newPos = PositionFromLocation(Point(lastXChosen, vs.lineHeight * (LinesToScroll() - caretYSlop))); + + } else { + Point pt = LocationFromPosition(currentPos); + + topLineNew = Platform::Clamp( + topLine + direction * LinesToScroll(), 0, MaxScrollPos()); + newPos = PositionFromLocation( + Point(lastXChosen, pt.y + direction * (vs.lineHeight * LinesToScroll()))); + } + + if (topLineNew != topLine) { + SetTopLine(topLineNew); + MovePositionTo(newPos, sel); + Redraw(); + SetVerticalScrollPos(); + } else { + MovePositionTo(newPos, sel); + } +} + +void Editor::ChangeCaseOfSelection(bool makeUpperCase) { + pdoc->BeginUndoAction(); + int startCurrent = currentPos; + int startAnchor = anchor; + if (selType == selStream) { + pdoc->ChangeCase(Range(SelectionStart(), SelectionEnd()), + makeUpperCase); + SetSelection(startCurrent, startAnchor); + } else { + SelectionLineIterator lineIterator(this, false); + while (lineIterator.Iterate()) { + pdoc->ChangeCase( + Range(lineIterator.startPos, lineIterator.endPos), + makeUpperCase); + } + // Would be nicer to keep the rectangular selection but this is complex + SetEmptySelection(startCurrent); + } + pdoc->EndUndoAction(); +} + +void Editor::LineTranspose() { + int line = pdoc->LineFromPosition(currentPos); + if (line > 0) { + int startPrev = pdoc->LineStart(line - 1); + int endPrev = pdoc->LineEnd(line - 1); + int start = pdoc->LineStart(line); + int end = pdoc->LineEnd(line); + int startNext = pdoc->LineStart(line + 1); + if (end < pdoc->Length()) { + end = startNext; + char *thisLine = CopyRange(start, end); + pdoc->DeleteChars(start, end - start); + if (pdoc->InsertString(startPrev, thisLine, end - start)) { + MovePositionTo(startPrev + end - start); + } + delete []thisLine; + } else { + // Last line so line has no line end + char *thisLine = CopyRange(start, end); + char *prevEnd = CopyRange(endPrev, start); + pdoc->DeleteChars(endPrev, end - endPrev); + pdoc->InsertString(startPrev, thisLine, end - start); + if (pdoc->InsertString(startPrev + end - start, prevEnd, start - endPrev)) { + MovePositionTo(startPrev + end - endPrev); + } + delete []thisLine; + delete []prevEnd; + } + + } +} + +void Editor::Duplicate(bool forLine) { + int start = SelectionStart(); + int end = SelectionEnd(); + if (start == end) { + forLine = true; + } + if (forLine) { + int line = pdoc->LineFromPosition(currentPos); + start = pdoc->LineStart(line); + end = pdoc->LineEnd(line); + } + char *text = CopyRange(start, end); + if (forLine) { + const char *eol = StringFromEOLMode(pdoc->eolMode); + pdoc->InsertString(end, eol); + pdoc->InsertString(end + istrlen(eol), text, end - start); + } else { + pdoc->InsertString(end, text, end - start); + } + delete []text; +} + +void Editor::CancelModes() { + moveExtendsSelection = false; +} + +void Editor::NewLine() { + 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 + if (pdoc->InsertString(currentPos, eol)) { + SetEmptySelection(currentPos + istrlen(eol)); + while (*eol) { + NotifyChar(*eol); + eol++; + } + } + SetLastXChosen(); + EnsureCaretVisible(); + // Avoid blinking during rapid typing: + ShowCaretAtCurrentPosition(); +} + +void Editor::CursorUpOrDown(int direction, selTypes sel) { + Point pt = LocationFromPosition(currentPos); + int posNew = PositionFromLocation( + Point(lastXChosen, pt.y + direction * vs.lineHeight)); + if (direction < 0) { + // Line wrapping may lead to a location on the same line, so + // seek back if that is the case. + // There is an equivalent case when moving down which skips + // over a line but as that does not trap the user it is fine. + Point ptNew = LocationFromPosition(posNew); + while ((posNew > 0) && (pt.y == ptNew.y)) { + posNew--; + ptNew = LocationFromPosition(posNew); + } + } + MovePositionTo(posNew, sel); +} + +void Editor::ParaUpOrDown(int direction, selTypes sel) { + int lineDoc, savedPos = currentPos; + do { + MovePositionTo(direction > 0 ? pdoc->ParaDown(currentPos) : pdoc->ParaUp(currentPos), sel); + lineDoc = pdoc->LineFromPosition(currentPos); + if (direction > 0) { + if (currentPos >= pdoc->Length() && !cs.GetVisible(lineDoc)) { + if (sel == noSel) { + MovePositionTo(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, selStream); + break; + case SCI_LINEDOWNRECTEXTEND: + CursorUpOrDown(1, selRectangle); + break; + case SCI_PARADOWN: + ParaUpOrDown(1); + break; + case SCI_PARADOWNEXTEND: + ParaUpOrDown(1, selStream); + break; + case SCI_LINESCROLLDOWN: + ScrollTo(topLine + 1); + MoveCaretInsideView(false); + break; + case SCI_LINEUP: + CursorUpOrDown(-1); + break; + case SCI_LINEUPEXTEND: + CursorUpOrDown(-1, selStream); + break; + case SCI_LINEUPRECTEXTEND: + CursorUpOrDown(-1, selRectangle); + break; + case SCI_PARAUP: + ParaUpOrDown(-1); + break; + case SCI_PARAUPEXTEND: + ParaUpOrDown(-1, selStream); + break; + case SCI_LINESCROLLUP: + ScrollTo(topLine - 1); + MoveCaretInsideView(false); + break; + case SCI_CHARLEFT: + if (SelectionEmpty() || moveExtendsSelection) { + MovePositionTo(MovePositionSoVisible(currentPos - 1, -1)); + } else { + MovePositionTo(SelectionStart()); + } + SetLastXChosen(); + break; + case SCI_CHARLEFTEXTEND: + MovePositionTo(MovePositionSoVisible(currentPos - 1, -1), selStream); + SetLastXChosen(); + break; + case SCI_CHARLEFTRECTEXTEND: + MovePositionTo(MovePositionSoVisible(currentPos - 1, -1), selRectangle); + SetLastXChosen(); + break; + case SCI_CHARRIGHT: + if (SelectionEmpty() || moveExtendsSelection) { + MovePositionTo(MovePositionSoVisible(currentPos + 1, 1)); + } else { + MovePositionTo(SelectionEnd()); + } + SetLastXChosen(); + break; + case SCI_CHARRIGHTEXTEND: + MovePositionTo(MovePositionSoVisible(currentPos + 1, 1), selStream); + SetLastXChosen(); + break; + case SCI_CHARRIGHTRECTEXTEND: + MovePositionTo(MovePositionSoVisible(currentPos + 1, 1), selRectangle); + SetLastXChosen(); + break; + case SCI_WORDLEFT: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, -1), -1)); + SetLastXChosen(); + break; + case SCI_WORDLEFTEXTEND: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, -1), -1), selStream); + SetLastXChosen(); + break; + case SCI_WORDRIGHT: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, 1), 1)); + SetLastXChosen(); + break; + case SCI_WORDRIGHTEXTEND: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, 1), 1), selStream); + SetLastXChosen(); + break; + + case SCI_WORDLEFTEND: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, -1), -1)); + SetLastXChosen(); + break; + case SCI_WORDLEFTENDEXTEND: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, -1), -1), selStream); + SetLastXChosen(); + break; + case SCI_WORDRIGHTEND: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, 1), 1)); + SetLastXChosen(); + break; + case SCI_WORDRIGHTENDEXTEND: + MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, 1), 1), selStream); + SetLastXChosen(); + break; + + case SCI_HOME: + MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos))); + SetLastXChosen(); + break; + case SCI_HOMEEXTEND: + MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos)), selStream); + SetLastXChosen(); + break; + case SCI_HOMERECTEXTEND: + MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos)), selRectangle); + SetLastXChosen(); + break; + case SCI_LINEEND: + MovePositionTo(pdoc->LineEndPosition(currentPos)); + SetLastXChosen(); + break; + case SCI_LINEENDEXTEND: + MovePositionTo(pdoc->LineEndPosition(currentPos), selStream); + SetLastXChosen(); + break; + case SCI_LINEENDRECTEXTEND: + MovePositionTo(pdoc->LineEndPosition(currentPos), selRectangle); + SetLastXChosen(); + break; + case SCI_HOMEWRAP: { + int homePos = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); + if (currentPos <= homePos) + homePos = pdoc->LineStart(pdoc->LineFromPosition(currentPos)); + MovePositionTo(homePos); + SetLastXChosen(); + } + break; + case SCI_HOMEWRAPEXTEND: { + int homePos = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); + if (currentPos <= homePos) + homePos = pdoc->LineStart(pdoc->LineFromPosition(currentPos)); + MovePositionTo(homePos, selStream); + SetLastXChosen(); + } + break; + case SCI_LINEENDWRAP: { + int endPos = MovePositionSoVisible(StartEndDisplayLine(currentPos, false), 1); + int realEndPos = pdoc->LineEndPosition(currentPos); + if (endPos > realEndPos // if moved past visible EOLs + || currentPos >= endPos) // if at end of display line already + endPos = realEndPos; + MovePositionTo(endPos); + SetLastXChosen(); + } + break; + case SCI_LINEENDWRAPEXTEND: { + int endPos = MovePositionSoVisible(StartEndDisplayLine(currentPos, false), 1); + int realEndPos = pdoc->LineEndPosition(currentPos); + if (endPos > realEndPos // if moved past visible EOLs + || currentPos >= endPos) // if at end of display line already + endPos = realEndPos; + MovePositionTo(endPos, selStream); + SetLastXChosen(); + } + break; + case SCI_DOCUMENTSTART: + MovePositionTo(0); + SetLastXChosen(); + break; + case SCI_DOCUMENTSTARTEXTEND: + MovePositionTo(0, selStream); + SetLastXChosen(); + break; + case SCI_DOCUMENTEND: + MovePositionTo(pdoc->Length()); + SetLastXChosen(); + break; + case SCI_DOCUMENTENDEXTEND: + MovePositionTo(pdoc->Length(), selStream); + SetLastXChosen(); + break; + case SCI_STUTTEREDPAGEUP: + PageMove(-1, noSel, true); + break; + case SCI_STUTTEREDPAGEUPEXTEND: + PageMove(-1, selStream, true); + break; + case SCI_STUTTEREDPAGEDOWN: + PageMove(1, noSel, true); + break; + case SCI_STUTTEREDPAGEDOWNEXTEND: + PageMove(1, selStream, true); + break; + case SCI_PAGEUP: + PageMove(-1); + break; + case SCI_PAGEUPEXTEND: + PageMove(-1, selStream); + break; + case SCI_PAGEUPRECTEXTEND: + PageMove(-1, selRectangle); + break; + case SCI_PAGEDOWN: + PageMove(1); + break; + case SCI_PAGEDOWNEXTEND: + PageMove(1, selStream); + break; + case SCI_PAGEDOWNRECTEXTEND: + PageMove(1, selRectangle); + break; + case SCI_EDITTOGGLEOVERTYPE: + inOverstrike = !inOverstrike; + DropCaret(); + ShowCaretAtCurrentPosition(); + NotifyUpdateUI(); + break; + case SCI_CANCEL: // Cancel any modes - handled in subclass + // Also unselect text + CancelModes(); + break; + case SCI_DELETEBACK: + DelCharBack(true); + if (!caretSticky) { + SetLastXChosen(); + } + EnsureCaretVisible(); + break; + case SCI_DELETEBACKNOTLINE: + DelCharBack(false); + if (!caretSticky) { + SetLastXChosen(); + } + EnsureCaretVisible(); + break; + case SCI_TAB: + Indent(true); + if (!caretSticky) { + SetLastXChosen(); + } + EnsureCaretVisible(); + break; + case SCI_BACKTAB: + Indent(false); + if (!caretSticky) { + SetLastXChosen(); + } + EnsureCaretVisible(); + break; + case SCI_NEWLINE: + NewLine(); + break; + case SCI_FORMFEED: + AddChar('\f'); + break; + case SCI_VCHOME: + MovePositionTo(pdoc->VCHomePosition(currentPos)); + SetLastXChosen(); + break; + case SCI_VCHOMEEXTEND: + MovePositionTo(pdoc->VCHomePosition(currentPos), selStream); + SetLastXChosen(); + break; + case SCI_VCHOMERECTEXTEND: + MovePositionTo(pdoc->VCHomePosition(currentPos), selRectangle); + SetLastXChosen(); + break; + case SCI_VCHOMEWRAP: { + int homePos = pdoc->VCHomePosition(currentPos); + int viewLineStart = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); + if ((viewLineStart < currentPos) && (viewLineStart > homePos)) + homePos = viewLineStart; + + MovePositionTo(homePos); + SetLastXChosen(); + } + break; + case SCI_VCHOMEWRAPEXTEND: { + int homePos = pdoc->VCHomePosition(currentPos); + int viewLineStart = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); + if ((viewLineStart < currentPos) && (viewLineStart > homePos)) + homePos = viewLineStart; + + MovePositionTo(homePos, selStream); + SetLastXChosen(); + } + break; + case SCI_ZOOMIN: + if (vs.zoomLevel < 20) { + vs.zoomLevel++; + InvalidateStyleRedraw(); + NotifyZoom(); + } + break; + case SCI_ZOOMOUT: + if (vs.zoomLevel > -10) { + vs.zoomLevel--; + InvalidateStyleRedraw(); + NotifyZoom(); + } + break; + case SCI_DELWORDLEFT: { + int startWord = pdoc->NextWordStart(currentPos, -1); + pdoc->DeleteChars(startWord, currentPos - startWord); + SetLastXChosen(); + } + break; + case SCI_DELWORDRIGHT: { + int endWord = pdoc->NextWordStart(currentPos, 1); + pdoc->DeleteChars(currentPos, endWord - currentPos); + } + break; + case SCI_DELLINELEFT: { + int line = pdoc->LineFromPosition(currentPos); + int start = pdoc->LineStart(line); + pdoc->DeleteChars(start, currentPos - start); + SetLastXChosen(); + } + break; + case SCI_DELLINERIGHT: { + int line = pdoc->LineFromPosition(currentPos); + int end = pdoc->LineEnd(line); + pdoc->DeleteChars(currentPos, end - currentPos); + } + break; + case SCI_LINECOPY: { + int lineStart = pdoc->LineFromPosition(SelectionStart()); + int lineEnd = pdoc->LineFromPosition(SelectionEnd()); + CopyRangeToClipboard(pdoc->LineStart(lineStart), + pdoc->LineStart(lineEnd + 1)); + } + break; + case SCI_LINECUT: { + int lineStart = pdoc->LineFromPosition(SelectionStart()); + int lineEnd = pdoc->LineFromPosition(SelectionEnd()); + int start = pdoc->LineStart(lineStart); + int end = pdoc->LineStart(lineEnd + 1); + SetSelection(start, end); + Cut(); + SetLastXChosen(); + } + break; + case SCI_LINEDELETE: { + int line = pdoc->LineFromPosition(currentPos); + int start = pdoc->LineStart(line); + int end = pdoc->LineStart(line + 1); + pdoc->DeleteChars(start, end - start); + } + break; + case SCI_LINETRANSPOSE: + LineTranspose(); + break; + case SCI_LINEDUPLICATE: + Duplicate(true); + break; + case SCI_SELECTIONDUPLICATE: + Duplicate(false); + break; + case SCI_LOWERCASE: + ChangeCaseOfSelection(false); + break; + case SCI_UPPERCASE: + ChangeCaseOfSelection(true); + break; + case SCI_WORDPARTLEFT: + MovePositionTo(MovePositionSoVisible(pdoc->WordPartLeft(currentPos), -1)); + SetLastXChosen(); + break; + case SCI_WORDPARTLEFTEXTEND: + MovePositionTo(MovePositionSoVisible(pdoc->WordPartLeft(currentPos), -1), selStream); + SetLastXChosen(); + break; + case SCI_WORDPARTRIGHT: + MovePositionTo(MovePositionSoVisible(pdoc->WordPartRight(currentPos), 1)); + SetLastXChosen(); + break; + case SCI_WORDPARTRIGHTEXTEND: + MovePositionTo(MovePositionSoVisible(pdoc->WordPartRight(currentPos), 1), selStream); + SetLastXChosen(); + break; + case SCI_HOMEDISPLAY: + MovePositionTo(MovePositionSoVisible( + StartEndDisplayLine(currentPos, true), -1)); + SetLastXChosen(); + break; + case SCI_HOMEDISPLAYEXTEND: + MovePositionTo(MovePositionSoVisible( + StartEndDisplayLine(currentPos, true), -1), selStream); + SetLastXChosen(); + break; + case SCI_LINEENDDISPLAY: + MovePositionTo(MovePositionSoVisible( + StartEndDisplayLine(currentPos, false), 1)); + SetLastXChosen(); + break; + case SCI_LINEENDDISPLAYEXTEND: + MovePositionTo(MovePositionSoVisible( + StartEndDisplayLine(currentPos, false), 1), selStream); + SetLastXChosen(); + break; + } + return 0; +} + +int Editor::KeyDefault(int, int) { + return 0; +} + +int Editor::KeyDown(int key, bool shift, bool ctrl, bool alt, bool *consumed) { + DwellEnd(false); + int modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | + (alt ? SCI_ALT : 0); + int msg = kmap.Find(key, modifiers); + if (msg) { + if (consumed) + *consumed = true; + return WndProc(msg, 0, 0); + } else { + if (consumed) + *consumed = false; + return KeyDefault(key, modifiers); + } +} + +void Editor::SetWhitespaceVisible(int view) { + vs.viewWhitespace = static_cast<WhiteSpaceVisibility>(view); +} + +int Editor::GetWhitespaceVisible() { + return vs.viewWhitespace; +} + +void Editor::Indent(bool forwards) { + //Platform::DebugPrintf("INdent %d\n", forwards); + int lineOfAnchor = pdoc->LineFromPosition(anchor); + int lineCurrentPos = pdoc->LineFromPosition(currentPos); + if (lineOfAnchor == lineCurrentPos) { + if (forwards) { + pdoc->BeginUndoAction(); + ClearSelection(); + if (pdoc->GetColumn(currentPos) <= pdoc->GetColumn(pdoc->GetLineIndentPosition(lineCurrentPos)) && + pdoc->tabIndents) { + int indentation = pdoc->GetLineIndentation(lineCurrentPos); + int indentationStep = pdoc->IndentSize(); + pdoc->SetLineIndentation(lineCurrentPos, indentation + indentationStep - indentation % indentationStep); + SetEmptySelection(pdoc->GetLineIndentPosition(lineCurrentPos)); + } else { + if (pdoc->useTabs) { + pdoc->InsertChar(currentPos, '\t'); + SetEmptySelection(currentPos + 1); + } else { + int numSpaces = (pdoc->tabInChars) - + (pdoc->GetColumn(currentPos) % (pdoc->tabInChars)); + if (numSpaces < 1) + numSpaces = pdoc->tabInChars; + for (int i = 0; i < numSpaces; i++) { + pdoc->InsertChar(currentPos + i, ' '); + } + SetEmptySelection(currentPos + numSpaces); + } + } + pdoc->EndUndoAction(); + } else { + if (pdoc->GetColumn(currentPos) <= pdoc->GetLineIndentation(lineCurrentPos) && + pdoc->tabIndents) { + pdoc->BeginUndoAction(); + int indentation = pdoc->GetLineIndentation(lineCurrentPos); + int indentationStep = pdoc->IndentSize(); + pdoc->SetLineIndentation(lineCurrentPos, indentation - indentationStep); + SetEmptySelection(pdoc->GetLineIndentPosition(lineCurrentPos)); + pdoc->EndUndoAction(); + } else { + int newColumn = ((pdoc->GetColumn(currentPos) - 1) / pdoc->tabInChars) * + pdoc->tabInChars; + if (newColumn < 0) + newColumn = 0; + int newPos = currentPos; + while (pdoc->GetColumn(newPos) > newColumn) + newPos--; + SetEmptySelection(newPos); + } + } + } else { + int anchorPosOnLine = anchor - pdoc->LineStart(lineOfAnchor); + int currentPosPosOnLine = currentPos - pdoc->LineStart(lineCurrentPos); + // Multiple lines selected so indent / dedent + int lineTopSel = Platform::Minimum(lineOfAnchor, lineCurrentPos); + int lineBottomSel = Platform::Maximum(lineOfAnchor, lineCurrentPos); + if (pdoc->LineStart(lineBottomSel) == anchor || pdoc->LineStart(lineBottomSel) == currentPos) + lineBottomSel--; // If not selecting any characters on a line, do not indent + pdoc->BeginUndoAction(); + pdoc->Indent(forwards, lineBottomSel, lineTopSel); + pdoc->EndUndoAction(); + if (lineOfAnchor < lineCurrentPos) { + if (currentPosPosOnLine == 0) + SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor)); + else + SetSelection(pdoc->LineStart(lineCurrentPos + 1), pdoc->LineStart(lineOfAnchor)); + } else { + if (anchorPosOnLine == 0) + SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor)); + else + SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor + 1)); + } + } +} + +/** + * Search of a text in the document, in the given range. + * @return The position of the found text, -1 if not found. + */ +long Editor::FindText( + uptr_t wParam, ///< Search modes : @c SCFIND_MATCHCASE, @c SCFIND_WHOLEWORD, + ///< @c SCFIND_WORDSTART, @c SCFIND_REGEXP or @c SCFIND_POSIX. + sptr_t lParam) { ///< @c TextToFind structure: The text to search for in the given range. + + TextToFind *ft = reinterpret_cast<TextToFind *>(lParam); + int lengthFound = istrlen(ft->lpstrText); + int pos = pdoc->FindText(ft->chrg.cpMin, ft->chrg.cpMax, ft->lpstrText, + (wParam & SCFIND_MATCHCASE) != 0, + (wParam & SCFIND_WHOLEWORD) != 0, + (wParam & SCFIND_WORDSTART) != 0, + (wParam & SCFIND_REGEXP) != 0, + (wParam & SCFIND_POSIX) != 0, + &lengthFound); + if (pos != -1) { + ft->chrgText.cpMin = pos; + ft->chrgText.cpMax = pos + lengthFound; + } + return pos; +} + +/** + * Relocatable search support : Searches relative to current selection + * point and sets the selection to the found text range with + * each search. + */ +/** + * Anchor following searches at current selection start: This allows + * multiple incremental interactive searches to be macro recorded + * while still setting the selection to found text so the find/select + * operation is self-contained. + */ +void Editor::SearchAnchor() { + searchAnchor = SelectionStart(); +} + +/** + * Find text from current search anchor: Must call @c SearchAnchor first. + * Used for next text and previous text requests. + * @return The position of the found text, -1 if not found. + */ +long Editor::SearchText( + unsigned int iMessage, ///< Accepts both @c SCI_SEARCHNEXT and @c SCI_SEARCHPREV. + uptr_t wParam, ///< Search modes : @c SCFIND_MATCHCASE, @c SCFIND_WHOLEWORD, + ///< @c SCFIND_WORDSTART, @c SCFIND_REGEXP or @c SCFIND_POSIX. + sptr_t lParam) { ///< The text to search for. + + const char *txt = reinterpret_cast<char *>(lParam); + int pos; + int lengthFound = istrlen(txt); + if (iMessage == SCI_SEARCHNEXT) { + pos = pdoc->FindText(searchAnchor, pdoc->Length(), txt, + (wParam & SCFIND_MATCHCASE) != 0, + (wParam & SCFIND_WHOLEWORD) != 0, + (wParam & SCFIND_WORDSTART) != 0, + (wParam & SCFIND_REGEXP) != 0, + (wParam & SCFIND_POSIX) != 0, + &lengthFound); + } else { + pos = pdoc->FindText(searchAnchor, 0, txt, + (wParam & SCFIND_MATCHCASE) != 0, + (wParam & SCFIND_WHOLEWORD) != 0, + (wParam & SCFIND_WORDSTART) != 0, + (wParam & SCFIND_REGEXP) != 0, + (wParam & SCFIND_POSIX) != 0, + &lengthFound); + } + + if (pos != -1) { + SetSelection(pos, pos + lengthFound); + } + + return pos; +} + +/** + * Search for text in the target range of the document. + * @return The position of the found text, -1 if not found. + */ +long Editor::SearchInTarget(const char *text, int length) { + int lengthFound = length; + int pos = pdoc->FindText(targetStart, targetEnd, text, + (searchFlags & SCFIND_MATCHCASE) != 0, + (searchFlags & SCFIND_WHOLEWORD) != 0, + (searchFlags & SCFIND_WORDSTART) != 0, + (searchFlags & SCFIND_REGEXP) != 0, + (searchFlags & SCFIND_POSIX) != 0, + &lengthFound); + if (pos != -1) { + targetStart = pos; + targetEnd = pos + lengthFound; + } + return pos; +} + +void Editor::GoToLine(int lineNo) { + if (lineNo > pdoc->LinesTotal()) + lineNo = pdoc->LinesTotal(); + if (lineNo < 0) + lineNo = 0; + SetEmptySelection(pdoc->LineStart(lineNo)); + ShowCaretAtCurrentPosition(); + EnsureCaretVisible(); +} + +static bool Close(Point pt1, Point pt2) { + if (abs(pt1.x - pt2.x) > 3) + return false; + if (abs(pt1.y - pt2.y) > 3) + return false; + return true; +} + +char *Editor::CopyRange(int start, int end) { + char *text = 0; + if (start < end) { + int len = end - start; + text = new char[len + 1]; + if (text) { + for (int i = 0; i < len; i++) { + text[i] = pdoc->CharAt(start + i); + } + text[len] = '\0'; + } + } + return text; +} + +void Editor::CopySelectionFromRange(SelectionText *ss, int start, int end) { + ss->Set(CopyRange(start, end), end - start + 1, + pdoc->dbcsCodePage, vs.styles[STYLE_DEFAULT].characterSet, false); +} + +void Editor::CopySelectionRange(SelectionText *ss) { + if (selType == selStream) { + CopySelectionFromRange(ss, SelectionStart(), SelectionEnd()); + } else { + char *text = 0; + int size = 0; + SelectionLineIterator lineIterator(this); + while (lineIterator.Iterate()) { + size += lineIterator.endPos - lineIterator.startPos; + if (selType != selLines) { + size++; + if (pdoc->eolMode == SC_EOL_CRLF) { + size++; + } + } + } + if (size > 0) { + text = new char[size + 1]; + if (text) { + int j = 0; + lineIterator.Reset(); + while (lineIterator.Iterate()) { + for (int i = lineIterator.startPos; + i < lineIterator.endPos; + i++) { + text[j++] = pdoc->CharAt(i); + } + if (selType != selLines) { + if (pdoc->eolMode != SC_EOL_LF) { + text[j++] = '\r'; + } + if (pdoc->eolMode != SC_EOL_CR) { + text[j++] = '\n'; + } + } + } + text[size] = '\0'; + } + } + ss->Set(text, size + 1, pdoc->dbcsCodePage, + vs.styles[STYLE_DEFAULT].characterSet, selType == selRectangle); + } +} + +void Editor::CopyRangeToClipboard(int start, int end) { + start = pdoc->ClampPositionIntoDocument(start); + end = pdoc->ClampPositionIntoDocument(end); + SelectionText selectedText; + selectedText.Set(CopyRange(start, end), end - start + 1, + pdoc->dbcsCodePage, vs.styles[STYLE_DEFAULT].characterSet, false); + CopyToClipboard(selectedText); +} + +void Editor::CopyText(int length, const char *text) { + SelectionText selectedText; + selectedText.Copy(text, length + 1, + pdoc->dbcsCodePage, vs.styles[STYLE_DEFAULT].characterSet, false); + CopyToClipboard(selectedText); +} + +void Editor::SetDragPosition(int newPos) { + if (newPos >= 0) { + newPos = MovePositionOutsideChar(newPos, 1); + posDrop = newPos; + } + if (posDrag != newPos) { + caret.on = true; + SetTicking(true); + InvalidateCaret(); + posDrag = newPos; + InvalidateCaret(); + } +} + +void Editor::DisplayCursor(Window::Cursor c) { + if (cursorMode == SC_CURSORNORMAL) + wMain.SetCursor(c); + else + wMain.SetCursor(static_cast<Window::Cursor>(cursorMode)); +} + +void Editor::StartDrag() { + // Always handled by subclasses + //SetMouseCapture(true); + //DisplayCursor(Window::cursorArrow); +} + +void Editor::DropAt(int position, const char *value, bool moving, bool rectangular) { + //Platform::DebugPrintf("DropAt %d\n", inDragDrop); + if (inDragDrop) + dropWentOutside = false; + + int positionWasInSelection = PositionInSelection(position); + + bool positionOnEdgeOfSelection = + (position == SelectionStart()) || (position == SelectionEnd()); + + if ((!inDragDrop) || !(0 == positionWasInSelection) || + (positionOnEdgeOfSelection && !moving)) { + + int selStart = SelectionStart(); + int selEnd = SelectionEnd(); + + pdoc->BeginUndoAction(); + + int positionAfterDeletion = position; + if (inDragDrop && moving) { + // Remove dragged out text + if (rectangular || selType == selLines) { + SelectionLineIterator lineIterator(this); + while (lineIterator.Iterate()) { + if (position >= lineIterator.startPos) { + if (position > lineIterator.endPos) { + positionAfterDeletion -= lineIterator.endPos - lineIterator.startPos; + } else { + positionAfterDeletion -= position - lineIterator.startPos; + } + } + } + } else { + if (position > selStart) { + positionAfterDeletion -= selEnd - selStart; + } + } + ClearSelection(); + } + position = positionAfterDeletion; + + if (rectangular) { + PasteRectangular(position, value, istrlen(value)); + pdoc->EndUndoAction(); + // Should try to select new rectangle but it may not be a rectangle now so just select the drop position + SetEmptySelection(position); + } else { + position = MovePositionOutsideChar(position, currentPos - position); + if (pdoc->InsertString(position, value)) { + SetSelection(position + istrlen(value), position); + } + pdoc->EndUndoAction(); + } + } else if (inDragDrop) { + SetEmptySelection(position); + } +} + +/** + * @return -1 if given position is before the selection, + * 1 if position is after the selection, + * 0 if position is inside the selection, + */ +int Editor::PositionInSelection(int pos) { + pos = MovePositionOutsideChar(pos, currentPos - pos); + if (pos < SelectionStart()) { + return -1; + } + if (pos > SelectionEnd()) { + return 1; + } + if (selType == selStream) { + return 0; + } else { + SelectionLineIterator lineIterator(this); + lineIterator.SetAt(pdoc->LineFromPosition(pos)); + if (pos < lineIterator.startPos) { + return -1; + } else if (pos > lineIterator.endPos) { + return 1; + } else { + return 0; + } + } +} + +bool Editor::PointInSelection(Point pt) { + int pos = PositionFromLocation(pt); + if (0 == PositionInSelection(pos)) { + // Probably inside, but we must make a finer test + int selStart, selEnd; + if (selType == selStream) { + selStart = SelectionStart(); + selEnd = SelectionEnd(); + } else { + SelectionLineIterator lineIterator(this); + lineIterator.SetAt(pdoc->LineFromPosition(pos)); + selStart = lineIterator.startPos; + selEnd = lineIterator.endPos; + } + if (pos == selStart) { + // see if just before selection + Point locStart = LocationFromPosition(pos); + if (pt.x < locStart.x) { + return false; + } + } + if (pos == selEnd) { + // see if just after selection + Point locEnd = LocationFromPosition(pos); + if (pt.x > locEnd.x) { + return false; + } + } + return true; + } + return false; +} + +bool Editor::PointInSelMargin(Point pt) { + // Really means: "Point in a margin" + if (vs.fixedColumnWidth > 0) { // There is a margin + PRectangle rcSelMargin = GetClientRectangle(); + rcSelMargin.right = vs.fixedColumnWidth - vs.leftMarginWidth; + return rcSelMargin.Contains(pt); + } else { + return false; + } +} + +void Editor::LineSelection(int lineCurrent_, int lineAnchor_) { + if (lineAnchor_ < lineCurrent_) { + SetSelection(pdoc->LineStart(lineCurrent_ + 1), + pdoc->LineStart(lineAnchor_)); + } else if (lineAnchor_ > lineCurrent_) { + SetSelection(pdoc->LineStart(lineCurrent_), + pdoc->LineStart(lineAnchor_ + 1)); + } else { // Same line, select it + SetSelection(pdoc->LineStart(lineAnchor_ + 1), + pdoc->LineStart(lineAnchor_)); + } +} + +void Editor::DwellEnd(bool mouseMoved) { + if (mouseMoved) + ticksToDwell = dwellDelay; + else + ticksToDwell = SC_TIME_FOREVER; + if (dwelling && (dwellDelay < SC_TIME_FOREVER)) { + dwelling = false; + NotifyDwelling(ptMouseLast, dwelling); + } +} + +void Editor::ButtonDown(Point pt, unsigned int curTime, bool shift, bool ctrl, bool alt) { + //Platform::DebugPrintf("Scintilla:ButtonDown %d %d = %d alt=%d\n", curTime, lastClickTime, curTime - lastClickTime, alt); + ptMouseLast = pt; + int newPos = PositionFromLocation(pt); + newPos = MovePositionOutsideChar(newPos, currentPos - newPos); + inDragDrop = false; + moveExtendsSelection = false; + + bool processed = NotifyMarginClick(pt, shift, ctrl, alt); + if (processed) + return; + + bool inSelMargin = PointInSelMargin(pt); + if (shift & !inSelMargin) { + SetSelection(newPos); + } + if (((curTime - lastClickTime) < Platform::DoubleClickTime()) && Close(pt, lastClick)) { + //Platform::DebugPrintf("Double click %d %d = %d\n", curTime, lastClickTime, curTime - lastClickTime); + SetMouseCapture(true); + SetEmptySelection(newPos); + bool doubleClick = false; + // Stop mouse button bounce changing selection type + if (!Platform::MouseButtonBounce() || curTime != lastClickTime) { + if (selectionType == selChar) { + selectionType = selWord; + doubleClick = true; + } else if (selectionType == selWord) { + selectionType = selLine; + } else { + selectionType = selChar; + originalAnchorPos = currentPos; + } + } + + if (selectionType == selWord) { + if (currentPos >= originalAnchorPos) { // Moved forward + SetSelection(pdoc->ExtendWordSelect(currentPos, 1), + pdoc->ExtendWordSelect(originalAnchorPos, -1)); + } else { // Moved backward + SetSelection(pdoc->ExtendWordSelect(currentPos, -1), + pdoc->ExtendWordSelect(originalAnchorPos, 1)); + } + } else if (selectionType == selLine) { + lineAnchor = LineFromLocation(pt); + SetSelection(pdoc->LineStart(lineAnchor + 1), pdoc->LineStart(lineAnchor)); + //Platform::DebugPrintf("Triple click: %d - %d\n", anchor, currentPos); + } else { + SetEmptySelection(currentPos); + } + //Platform::DebugPrintf("Double click: %d - %d\n", anchor, currentPos); + if (doubleClick) { + NotifyDoubleClick(pt, shift); + if (PositionIsHotspot(newPos)) + NotifyHotSpotDoubleClicked(newPos, shift, ctrl, alt); + } + } else { // Single click + if (inSelMargin) { + selType = selStream; + if (ctrl) { + SelectAll(); + lastClickTime = curTime; + return; + } + if (!shift) { + lineAnchor = LineFromLocation(pt); + // Single click in margin: select whole line + LineSelection(lineAnchor, lineAnchor); + SetSelection(pdoc->LineStart(lineAnchor + 1), + pdoc->LineStart(lineAnchor)); + } else { + // Single shift+click in margin: select from line anchor to clicked line + if (anchor > currentPos) + lineAnchor = pdoc->LineFromPosition(anchor - 1); + else + lineAnchor = pdoc->LineFromPosition(anchor); + int lineStart = LineFromLocation(pt); + LineSelection(lineStart, lineAnchor); + //lineAnchor = lineStart; // Keep the same anchor for ButtonMove + } + + SetDragPosition(invalidPosition); + SetMouseCapture(true); + selectionType = selLine; + } else { + if (PointIsHotspot(pt)) { + NotifyHotSpotClicked(newPos, shift, ctrl, alt); + } + if (!shift) { + inDragDrop = PointInSelection(pt) && !SelectionEmpty(); + } + if (inDragDrop) { + SetMouseCapture(false); + SetDragPosition(newPos); + CopySelectionRange(&drag); + StartDrag(); + } else { + SetDragPosition(invalidPosition); + SetMouseCapture(true); + if (!shift) { + SetEmptySelection(newPos); + } + selType = alt ? selRectangle : selStream; + selectionType = selChar; + originalAnchorPos = currentPos; + SetRectangularRange(); + } + } + } + lastClickTime = curTime; + lastXChosen = pt.x; + ShowCaretAtCurrentPosition(); +} + +bool Editor::PositionIsHotspot(int position) { + return vs.styles[pdoc->StyleAt(position) & pdoc->stylingBitsMask].hotspot; +} + +bool Editor::PointIsHotspot(Point pt) { + int pos = PositionFromLocationClose(pt); + if (pos == INVALID_POSITION) + return false; + return PositionIsHotspot(pos); +} + +void Editor::SetHotSpotRange(Point *pt) { + if (pt) { + int pos = PositionFromLocation(*pt); + + // If we don't limit this to word characters then the + // range can encompass more than the run range and then + // the underline will not be drawn properly. + int hsStart_ = pdoc->ExtendStyleRange(pos, -1, vs.hotspotSingleLine); + int hsEnd_ = pdoc->ExtendStyleRange(pos, 1, vs.hotspotSingleLine); + + // Only invalidate the range if the hotspot range has changed... + if (hsStart_ != hsStart || hsEnd_ != hsEnd) { + if (hsStart != -1) { + InvalidateRange(hsStart, hsEnd); + } + hsStart = hsStart_; + hsEnd = hsEnd_; + InvalidateRange(hsStart, hsEnd); + } + } else { + if (hsStart != -1) { + int hsStart_ = hsStart; + int hsEnd_ = hsEnd; + hsStart = -1; + hsEnd = -1; + InvalidateRange(hsStart_, hsEnd_); + } else { + hsStart = -1; + hsEnd = -1; + } + } +} + +void Editor::GetHotSpotRange(int& hsStart_, int& hsEnd_) { + hsStart_ = hsStart; + hsEnd_ = hsEnd; +} + +void Editor::ButtonMove(Point pt) { + if ((ptMouseLast.x != pt.x) || (ptMouseLast.y != pt.y)) { + DwellEnd(true); + } + ptMouseLast = pt; + //Platform::DebugPrintf("Move %d %d\n", pt.x, pt.y); + if (HaveMouseCapture()) { + + // Slow down autoscrolling/selection + autoScrollTimer.ticksToWait -= timer.tickSize; + if (autoScrollTimer.ticksToWait > 0) + return; + autoScrollTimer.ticksToWait = autoScrollDelay; + + // Adjust selection + int movePos = PositionFromLocation(pt); + movePos = MovePositionOutsideChar(movePos, currentPos - movePos); + if (posDrag >= 0) { + SetDragPosition(movePos); + } else { + if (selectionType == selChar) { + SetSelection(movePos); + } else if (selectionType == selWord) { + // Continue selecting by word + if (movePos == originalAnchorPos) { // Didn't move + // No need to do anything. Previously this case was lumped + // in with "Moved forward", but that can be harmful in this + // case: a handler for the NotifyDoubleClick re-adjusts + // the selection for a fancier definition of "word" (for + // example, in Perl it is useful to include the leading + // '$', '%' or '@' on variables for word selection). In this + // the ButtonMove() called via Tick() for auto-scrolling + // could result in the fancier word selection adjustment + // being unmade. + } else if (movePos > originalAnchorPos) { // Moved forward + SetSelection(pdoc->ExtendWordSelect(movePos, 1), + pdoc->ExtendWordSelect(originalAnchorPos, -1)); + } else { // Moved backward + SetSelection(pdoc->ExtendWordSelect(movePos, -1), + pdoc->ExtendWordSelect(originalAnchorPos, 1)); + } + } else { + // Continue selecting by line + int lineMove = LineFromLocation(pt); + LineSelection(lineMove, lineAnchor); + } + } + // While dragging to make rectangular selection, we don't want the current + // position to jump to the end of smaller or empty lines. + //xEndSelect = pt.x - vs.fixedColumnWidth + xOffset; + xEndSelect = XFromPosition(movePos); + + // Autoscroll + PRectangle rcClient = GetClientRectangle(); + if (pt.y > rcClient.bottom) { + int lineMove = cs.DisplayFromDoc(LineFromLocation(pt)); + if (lineMove < 0) { + lineMove = cs.DisplayFromDoc(pdoc->LinesTotal() - 1); + } + ScrollTo(lineMove - LinesOnScreen() + 5); + Redraw(); + } else if (pt.y < rcClient.top) { + int lineMove = cs.DisplayFromDoc(LineFromLocation(pt)); + ScrollTo(lineMove - 5); + Redraw(); + } + EnsureCaretVisible(false, false, true); + + if (hsStart != -1 && !PositionIsHotspot(movePos)) + SetHotSpotRange(NULL); + + } else { + if (vs.fixedColumnWidth > 0) { // There is a margin + if (PointInSelMargin(pt)) { + DisplayCursor(Window::cursorReverseArrow); + return; // No need to test for selection + } + } + // Display regular (drag) cursor over selection + if (PointInSelection(pt) && !SelectionEmpty()) { + DisplayCursor(Window::cursorArrow); + } else if (PointIsHotspot(pt)) { + DisplayCursor(Window::cursorHand); + SetHotSpotRange(&pt); + } else { + DisplayCursor(Window::cursorText); + SetHotSpotRange(NULL); + } + } +} + +void Editor::ButtonUp(Point pt, unsigned int curTime, bool ctrl) { + //Platform::DebugPrintf("ButtonUp %d\n", HaveMouseCapture()); + if (HaveMouseCapture()) { + if (PointInSelMargin(pt)) { + DisplayCursor(Window::cursorReverseArrow); + } else { + DisplayCursor(Window::cursorText); + SetHotSpotRange(NULL); + } + ptMouseLast = pt; + SetMouseCapture(false); + int newPos = PositionFromLocation(pt); + newPos = MovePositionOutsideChar(newPos, currentPos - newPos); + if (inDragDrop) { + int selStart = SelectionStart(); + int selEnd = SelectionEnd(); + if (selStart < selEnd) { + if (drag.len) { + if (ctrl) { + if (pdoc->InsertString(newPos, drag.s, drag.len)) { + SetSelection(newPos, newPos + drag.len); + } + } else if (newPos < selStart) { + pdoc->DeleteChars(selStart, drag.len); + if (pdoc->InsertString(newPos, drag.s, drag.len)) { + SetSelection(newPos, newPos + drag.len); + } + } else if (newPos > selEnd) { + pdoc->DeleteChars(selStart, drag.len); + newPos -= drag.len; + if (pdoc->InsertString(newPos, drag.s, drag.len)) { + SetSelection(newPos, newPos + drag.len); + } + } else { + SetEmptySelection(newPos); + } + drag.Free(); + } + selectionType = selChar; + } + } else { + if (selectionType == selChar) { + SetSelection(newPos); + } + } + SetRectangularRange(); + lastClickTime = curTime; + lastClick = pt; + lastXChosen = pt.x; + if (selType == selStream) { + SetLastXChosen(); + } + inDragDrop = false; + EnsureCaretVisible(false); + } +} + +// Called frequently to perform background UI including +// caret blinking and automatic scrolling. +void Editor::Tick() { + if (HaveMouseCapture()) { + // Auto scroll + ButtonMove(ptMouseLast); + } + if (caret.period > 0) { + timer.ticksToWait -= timer.tickSize; + if (timer.ticksToWait <= 0) { + caret.on = !caret.on; + timer.ticksToWait = caret.period; + if (caret.active) { + InvalidateCaret(); + } + } + } + if ((dwellDelay < SC_TIME_FOREVER) && + (ticksToDwell > 0) && + (!HaveMouseCapture())) { + ticksToDwell -= timer.tickSize; + if (ticksToDwell <= 0) { + dwelling = true; + NotifyDwelling(ptMouseLast, dwelling); + } + } +} + +bool Editor::Idle() { + + bool idleDone; + + bool wrappingDone = wrapState == eWrapNone; + + if (!wrappingDone) { + // Wrap lines during idle. + WrapLines(false, -1); + // No more wrapping + if (wrapStart == wrapEnd) + wrappingDone = true; + } + + // Add more idle things to do here, but make sure idleDone is + // set correctly before the function returns. returning + // false will stop calling this idle funtion until SetIdle() is + // called again. + + idleDone = wrappingDone; // && thatDone && theOtherThingDone... + + return !idleDone; +} + +void Editor::SetFocusState(bool focusState) { + hasFocus = focusState; + NotifyFocus(hasFocus); + if (hasFocus) { + ShowCaretAtCurrentPosition(); + } else { + CancelModes(); + DropCaret(); + } +} + +bool Editor::PaintContains(PRectangle rc) { + return rcPaint.Contains(rc); +} + +bool Editor::PaintContainsMargin() { + PRectangle rcSelMargin = GetClientRectangle(); + rcSelMargin.right = vs.fixedColumnWidth; + return PaintContains(rcSelMargin); +} + +void Editor::CheckForChangeOutsidePaint(Range r) { + if (paintState == painting && !paintingAllText) { + //Platform::DebugPrintf("Checking range in paint %d-%d\n", r.start, r.end); + if (!r.Valid()) + return; + + PRectangle rcRange = RectangleFromRange(r.start, r.end); + PRectangle rcText = GetTextRectangle(); + if (rcRange.top < rcText.top) { + rcRange.top = rcText.top; + } + if (rcRange.bottom > rcText.bottom) { + rcRange.bottom = rcText.bottom; + } + + if (!PaintContains(rcRange)) { + AbandonPaint(); + } + } +} + +void Editor::SetBraceHighlight(Position pos0, Position pos1, int matchStyle) { + if ((pos0 != braces[0]) || (pos1 != braces[1]) || (matchStyle != bracesMatchStyle)) { + if ((braces[0] != pos0) || (matchStyle != bracesMatchStyle)) { + CheckForChangeOutsidePaint(Range(braces[0])); + CheckForChangeOutsidePaint(Range(pos0)); + braces[0] = pos0; + } + if ((braces[1] != pos1) || (matchStyle != bracesMatchStyle)) { + CheckForChangeOutsidePaint(Range(braces[1])); + CheckForChangeOutsidePaint(Range(pos1)); + braces[1] = pos1; + } + bracesMatchStyle = matchStyle; + if (paintState == notPainting) { + Redraw(); + } + } +} + +void Editor::SetDocPointer(Document *document) { + //Platform::DebugPrintf("** %x setdoc to %x\n", pdoc, document); + pdoc->RemoveWatcher(this, 0); + pdoc->Release(); + if (document == NULL) { + pdoc = new Document(); + } else { + pdoc = document; + } + pdoc->AddRef(); + + // Ensure all positions within document + selType = selStream; + currentPos = 0; + anchor = 0; + targetStart = 0; + targetEnd = 0; + + braces[0] = invalidPosition; + braces[1] = invalidPosition; + + // Reset the contraction state to fully shown. + cs.Clear(); + cs.InsertLines(0, pdoc->LinesTotal() - 1); + llc.Deallocate(); + NeedWrapping(); + + pdoc->AddWatcher(this, 0); + SetScrollBars(); + Redraw(); +} + +/** + * Recursively expand a fold, making lines visible except where they have an unexpanded parent. + */ +void Editor::Expand(int &line, bool doExpand) { + int lineMaxSubord = pdoc->GetLastChild(line); + line++; + while (line <= lineMaxSubord) { + if (doExpand) + cs.SetVisible(line, line, true); + int level = pdoc->GetLevel(line); + if (level & SC_FOLDLEVELHEADERFLAG) { + if (doExpand && cs.GetExpanded(line)) { + Expand(line, true); + } else { + Expand(line, false); + } + } else { + line++; + } + } +} + +void Editor::ToggleContraction(int line) { + if (line >= 0) { + if ((pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) == 0) { + line = pdoc->GetFoldParent(line); + if (line < 0) + return; + } + + if (cs.GetExpanded(line)) { + int lineMaxSubord = pdoc->GetLastChild(line); + cs.SetExpanded(line, 0); + if (lineMaxSubord > line) { + cs.SetVisible(line + 1, lineMaxSubord, false); + + int lineCurrent = pdoc->LineFromPosition(currentPos); + if (lineCurrent > line && lineCurrent <= lineMaxSubord) { + // This does not re-expand the fold + EnsureCaretVisible(); + } + + SetScrollBars(); + Redraw(); + } + + } else { + if (!(cs.GetVisible(line))) { + EnsureLineVisible(line, false); + GoToLine(line); + } + cs.SetExpanded(line, 1); + Expand(line, true); + SetScrollBars(); + Redraw(); + } + } +} + +/** + * Recurse up from this line to find any folds that prevent this line from being visible + * and unfold them all. + */ +void Editor::EnsureLineVisible(int lineDoc, bool enforcePolicy) { + + // In case in need of wrapping to ensure DisplayFromDoc works. + WrapLines(true, -1); + + if (!cs.GetVisible(lineDoc)) { + int lineParent = pdoc->GetFoldParent(lineDoc); + if (lineParent >= 0) { + if (lineDoc != lineParent) + EnsureLineVisible(lineParent, enforcePolicy); + if (!cs.GetExpanded(lineParent)) { + cs.SetExpanded(lineParent, 1); + Expand(lineParent, true); + } + } + SetScrollBars(); + Redraw(); + } + if (enforcePolicy) { + int lineDisplay = cs.DisplayFromDoc(lineDoc); + if (visiblePolicy & VISIBLE_SLOP) { + if ((topLine > lineDisplay) || ((visiblePolicy & VISIBLE_STRICT) && (topLine + visibleSlop > lineDisplay))) { + SetTopLine(Platform::Clamp(lineDisplay - visibleSlop, 0, MaxScrollPos())); + SetVerticalScrollPos(); + Redraw(); + } else if ((lineDisplay > topLine + LinesOnScreen() - 1) || + ((visiblePolicy & VISIBLE_STRICT) && (lineDisplay > topLine + LinesOnScreen() - 1 - visibleSlop))) { + SetTopLine(Platform::Clamp(lineDisplay - LinesOnScreen() + 1 + visibleSlop, 0, MaxScrollPos())); + SetVerticalScrollPos(); + Redraw(); + } + } else { + if ((topLine > lineDisplay) || (lineDisplay > topLine + LinesOnScreen() - 1) || (visiblePolicy & VISIBLE_STRICT)) { + SetTopLine(Platform::Clamp(lineDisplay - LinesOnScreen() / 2 + 1, 0, MaxScrollPos())); + SetVerticalScrollPos(); + Redraw(); + } + } + } +} + +int Editor::ReplaceTarget(bool replacePatterns, const char *text, int length) { + pdoc->BeginUndoAction(); + if (length == -1) + length = istrlen(text); + if (replacePatterns) { + text = pdoc->SubstituteByPosition(text, &length); + if (!text) + return 0; + } + if (targetStart != targetEnd) + pdoc->DeleteChars(targetStart, targetEnd - targetStart); + targetEnd = targetStart; + pdoc->InsertString(targetStart, text, length); + targetEnd = targetStart + length; + pdoc->EndUndoAction(); + return length; +} + +bool Editor::IsUnicodeMode() const { + return pdoc && (SC_CP_UTF8 == pdoc->dbcsCodePage); +} + +int Editor::CodePage() const { + if (pdoc) + return pdoc->dbcsCodePage; + else + return 0; +} + +int Editor::WrapCount(int line) { + AutoSurface surface(this); + AutoLineLayout ll(llc, RetrieveLineLayout(line)); + + if (surface && ll) { + LayoutLine(line, surface, vs, ll, wrapWidth); + return ll->lines; + } else { + return 1; + } +} + +static bool ValidMargin(unsigned long wParam) { + return wParam < ViewStyle::margins; +} + +static char *CharPtrFromSPtr(sptr_t lParam) { + return reinterpret_cast<char *>(lParam); +} + +sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { + //Platform::DebugPrintf("S start wnd proc %d %d %d\n",iMessage, wParam, lParam); + + // Optional macro recording hook + if (recordingMacro) + NotifyMacroRecord(iMessage, wParam, lParam); + + switch (iMessage) { + + case SCI_GETTEXT: { + if (lParam == 0) + return pdoc->Length() + 1; + if (wParam == 0) + return 0; + char *ptr = CharPtrFromSPtr(lParam); + unsigned int iChar = 0; + for (; iChar < wParam - 1; iChar++) + ptr[iChar] = pdoc->CharAt(iChar); + ptr[iChar] = '\0'; + return iChar; + } + + case SCI_SETTEXT: { + if (lParam == 0) + return 0; + pdoc->BeginUndoAction(); + pdoc->DeleteChars(0, pdoc->Length()); + SetEmptySelection(0); + pdoc->InsertString(0, CharPtrFromSPtr(lParam)); + pdoc->EndUndoAction(); + return 1; + } + + case SCI_GETTEXTLENGTH: + return pdoc->Length(); + + case SCI_CUT: + Cut(); + SetLastXChosen(); + break; + + case SCI_COPY: + Copy(); + break; + + case SCI_COPYRANGE: + CopyRangeToClipboard(wParam, lParam); + break; + + case SCI_COPYTEXT: + CopyText(wParam, CharPtrFromSPtr(lParam)); + break; + + case SCI_PASTE: + Paste(); + if (!caretSticky) { + SetLastXChosen(); + } + EnsureCaretVisible(); + break; + + case SCI_CLEAR: + Clear(); + SetLastXChosen(); + EnsureCaretVisible(); + break; + + case SCI_UNDO: + Undo(); + SetLastXChosen(); + break; + + case SCI_CANUNDO: + return (pdoc->CanUndo() && !pdoc->IsReadOnly()) ? 1 : 0; + + case SCI_EMPTYUNDOBUFFER: + pdoc->DeleteUndoHistory(); + return 0; + + case SCI_GETFIRSTVISIBLELINE: + return topLine; + + case SCI_GETLINE: { // Risk of overwriting the end of the buffer + int lineStart = pdoc->LineStart(wParam); + int lineEnd = pdoc->LineStart(wParam + 1); + if (lParam == 0) { + return lineEnd - lineStart; + } + char *ptr = CharPtrFromSPtr(lParam); + int iPlace = 0; + for (int iChar = lineStart; iChar < lineEnd; iChar++) { + ptr[iPlace++] = pdoc->CharAt(iChar); + } + return iPlace; + } + + case SCI_GETLINECOUNT: + if (pdoc->LinesTotal() == 0) + return 1; + else + return pdoc->LinesTotal(); + + case SCI_GETMODIFY: + return !pdoc->IsSavePoint(); + + case SCI_SETSEL: { + int nStart = static_cast<int>(wParam); + int nEnd = static_cast<int>(lParam); + if (nEnd < 0) + nEnd = pdoc->Length(); + if (nStart < 0) + nStart = nEnd; // Remove selection + selType = selStream; + SetSelection(nEnd, nStart); + EnsureCaretVisible(); + } + break; + + case SCI_GETSELTEXT: { + if (lParam == 0) { + if (selType == selStream) { + return 1 + SelectionEnd() - SelectionStart(); + } else { + // TODO: why is selLines handled the slow way? + int size = 0; + int extraCharsPerLine = 0; + if (selType != selLines) + extraCharsPerLine = (pdoc->eolMode == SC_EOL_CRLF) ? 2 : 1; + SelectionLineIterator lineIterator(this); + while (lineIterator.Iterate()) { + size += lineIterator.endPos + extraCharsPerLine - lineIterator.startPos; + } + + return 1 + size; + } + } + SelectionText selectedText; + CopySelectionRange(&selectedText); + char *ptr = CharPtrFromSPtr(lParam); + int iChar = 0; + if (selectedText.len) { + for (; iChar < selectedText.len; iChar++) + ptr[iChar] = selectedText.s[iChar]; + } else { + ptr[0] = '\0'; + } + return iChar; + } + + case SCI_LINEFROMPOSITION: + if (static_cast<int>(wParam) < 0) + return 0; + return pdoc->LineFromPosition(wParam); + + case SCI_POSITIONFROMLINE: + if (static_cast<int>(wParam) < 0) + wParam = pdoc->LineFromPosition(SelectionStart()); + if (wParam == 0) + return 0; // Even if there is no text, there is a first line that starts at 0 + if (static_cast<int>(wParam) > pdoc->LinesTotal()) + return -1; + //if (wParam > pdoc->LineFromPosition(pdoc->Length())) // Useful test, anyway... + // return -1; + return pdoc->LineStart(wParam); + + // Replacement of the old Scintilla interpretation of EM_LINELENGTH + case SCI_LINELENGTH: + if ((static_cast<int>(wParam) < 0) || + (static_cast<int>(wParam) > pdoc->LineFromPosition(pdoc->Length()))) + return 0; + return pdoc->LineStart(wParam + 1) - pdoc->LineStart(wParam); + + case SCI_REPLACESEL: { + if (lParam == 0) + return 0; + pdoc->BeginUndoAction(); + ClearSelection(); + char *replacement = CharPtrFromSPtr(lParam); + pdoc->InsertString(currentPos, replacement); + pdoc->EndUndoAction(); + SetEmptySelection(currentPos + istrlen(replacement)); + EnsureCaretVisible(); + } + break; + + case SCI_SETTARGETSTART: + targetStart = wParam; + break; + + case SCI_GETTARGETSTART: + return targetStart; + + case SCI_SETTARGETEND: + targetEnd = wParam; + break; + + case SCI_GETTARGETEND: + return targetEnd; + + case SCI_TARGETFROMSELECTION: + if (currentPos < anchor) { + targetStart = currentPos; + targetEnd = anchor; + } else { + targetStart = anchor; + targetEnd = currentPos; + } + break; + + case SCI_REPLACETARGET: + PLATFORM_ASSERT(lParam); + return ReplaceTarget(false, CharPtrFromSPtr(lParam), wParam); + + case SCI_REPLACETARGETRE: + PLATFORM_ASSERT(lParam); + return ReplaceTarget(true, CharPtrFromSPtr(lParam), wParam); + + case SCI_SEARCHINTARGET: + PLATFORM_ASSERT(lParam); + return SearchInTarget(CharPtrFromSPtr(lParam), wParam); + + case SCI_SETSEARCHFLAGS: + searchFlags = wParam; + break; + + case SCI_GETSEARCHFLAGS: + return searchFlags; + + case SCI_POSITIONBEFORE: + return pdoc->MovePositionOutsideChar(wParam-1, -1, true); + + case SCI_POSITIONAFTER: + return pdoc->MovePositionOutsideChar(wParam+1, 1, true); + + case SCI_LINESCROLL: + ScrollTo(topLine + lParam); + HorizontalScrollTo(xOffset + wParam * vs.spaceWidth); + return 1; + + case SCI_SETXOFFSET: + xOffset = wParam; + SetHorizontalScrollPos(); + Redraw(); + break; + + case SCI_GETXOFFSET: + return xOffset; + + case SCI_CHOOSECARETX: + SetLastXChosen(); + break; + + case SCI_SCROLLCARET: + EnsureCaretVisible(); + break; + + case SCI_SETREADONLY: + pdoc->SetReadOnly(wParam != 0); + return 1; + + case SCI_GETREADONLY: + return pdoc->IsReadOnly(); + + case SCI_CANPASTE: + return CanPaste(); + + case SCI_POINTXFROMPOSITION: + if (lParam < 0) { + return 0; + } else { + Point pt = LocationFromPosition(lParam); + return pt.x; + } + + case SCI_POINTYFROMPOSITION: + if (lParam < 0) { + return 0; + } else { + Point pt = LocationFromPosition(lParam); + return pt.y; + } + + case SCI_FINDTEXT: + return FindText(wParam, lParam); + + case SCI_GETTEXTRANGE: { + if (lParam == 0) + return 0; + TextRange *tr = reinterpret_cast<TextRange *>(lParam); + int cpMax = tr->chrg.cpMax; + if (cpMax == -1) + cpMax = pdoc->Length(); + PLATFORM_ASSERT(cpMax <= pdoc->Length()); + int len = cpMax - tr->chrg.cpMin; // No -1 as cpMin and cpMax are referring to inter character positions + pdoc->GetCharRange(tr->lpstrText, tr->chrg.cpMin, len); + // Spec says copied text is terminated with a NUL + tr->lpstrText[len] = '\0'; + return len; // Not including NUL + } + + case SCI_HIDESELECTION: + hideSelection = wParam != 0; + Redraw(); + break; + + case SCI_FORMATRANGE: + return FormatRange(wParam != 0, reinterpret_cast<RangeToFormat *>(lParam)); + + case SCI_GETMARGINLEFT: + return vs.leftMarginWidth; + + case SCI_GETMARGINRIGHT: + return vs.rightMarginWidth; + + case SCI_SETMARGINLEFT: + vs.leftMarginWidth = lParam; + InvalidateStyleRedraw(); + break; + + case SCI_SETMARGINRIGHT: + vs.rightMarginWidth = lParam; + InvalidateStyleRedraw(); + break; + + // Control specific mesages + + case SCI_ADDTEXT: { + if (lParam == 0) + return 0; + pdoc->InsertString(CurrentPosition(), CharPtrFromSPtr(lParam), wParam); + SetEmptySelection(currentPos + wParam); + return 0; + } + + case SCI_ADDSTYLEDTEXT: { + if (lParam == 0) + return 0; + pdoc->InsertStyledString(CurrentPosition() * 2, CharPtrFromSPtr(lParam), wParam); + SetEmptySelection(currentPos + wParam / 2); + return 0; + } + + case SCI_INSERTTEXT: { + if (lParam == 0) + return 0; + int insertPos = wParam; + if (static_cast<int>(wParam) == -1) + insertPos = CurrentPosition(); + int newCurrent = CurrentPosition(); + char *sz = CharPtrFromSPtr(lParam); + pdoc->InsertString(insertPos, sz); + if (newCurrent > insertPos) + newCurrent += istrlen(sz); + SetEmptySelection(newCurrent); + return 0; + } + + case SCI_APPENDTEXT: + pdoc->InsertString(pdoc->Length(), CharPtrFromSPtr(lParam), wParam); + return 0; + + case SCI_CLEARALL: + ClearAll(); + return 0; + + case SCI_CLEARDOCUMENTSTYLE: + ClearDocumentStyle(); + return 0; + + case SCI_SETUNDOCOLLECTION: + pdoc->SetUndoCollection(wParam != 0); + return 0; + + case SCI_GETUNDOCOLLECTION: + return pdoc->IsCollectingUndo(); + + case SCI_BEGINUNDOACTION: + pdoc->BeginUndoAction(); + return 0; + + case SCI_ENDUNDOACTION: + pdoc->EndUndoAction(); + return 0; + + case SCI_GETCARETPERIOD: + return caret.period; + + case SCI_SETCARETPERIOD: + caret.period = wParam; + break; + + case SCI_SETWORDCHARS: { + pdoc->SetDefaultCharClasses(false); + if (lParam == 0) + return 0; + pdoc->SetCharClasses(reinterpret_cast<unsigned char *>(lParam), CharClassify::ccWord); + } + break; + + case SCI_SETWHITESPACECHARS: { + if (lParam == 0) + return 0; + pdoc->SetCharClasses(reinterpret_cast<unsigned char *>(lParam), CharClassify::ccSpace); + } + break; + + case SCI_SETCHARSDEFAULT: + pdoc->SetDefaultCharClasses(true); + break; + + case SCI_GETLENGTH: + return pdoc->Length(); + + case SCI_ALLOCATE: + pdoc->Allocate(wParam); + break; + + case SCI_GETCHARAT: + return pdoc->CharAt(wParam); + + case SCI_SETCURRENTPOS: + SetSelection(wParam, anchor); + break; + + case SCI_GETCURRENTPOS: + return currentPos; + + case SCI_SETANCHOR: + SetSelection(currentPos, wParam); + break; + + case SCI_GETANCHOR: + return anchor; + + case SCI_SETSELECTIONSTART: + SetSelection(Platform::Maximum(currentPos, wParam), wParam); + break; + + case SCI_GETSELECTIONSTART: + return Platform::Minimum(anchor, currentPos); + + case SCI_SETSELECTIONEND: + SetSelection(wParam, Platform::Minimum(anchor, wParam)); + break; + + case SCI_GETSELECTIONEND: + return Platform::Maximum(anchor, currentPos); + + case SCI_SETPRINTMAGNIFICATION: + printMagnification = wParam; + break; + + case SCI_GETPRINTMAGNIFICATION: + return printMagnification; + + case SCI_SETPRINTCOLOURMODE: + printColourMode = wParam; + break; + + case SCI_GETPRINTCOLOURMODE: + return printColourMode; + + case SCI_SETPRINTWRAPMODE: + printWrapState = (wParam == SC_WRAP_WORD) ? eWrapWord : eWrapNone; + break; + + case SCI_GETPRINTWRAPMODE: + return printWrapState; + + case SCI_GETSTYLEAT: + if (static_cast<int>(wParam) >= pdoc->Length()) + return 0; + else + return pdoc->StyleAt(wParam); + + case SCI_REDO: + Redo(); + break; + + case SCI_SELECTALL: + SelectAll(); + break; + + case SCI_SETSAVEPOINT: + pdoc->SetSavePoint(); + break; + + case SCI_GETSTYLEDTEXT: { + if (lParam == 0) + return 0; + TextRange *tr = reinterpret_cast<TextRange *>(lParam); + int iPlace = 0; + for (int iChar = tr->chrg.cpMin; iChar < tr->chrg.cpMax; iChar++) { + tr->lpstrText[iPlace++] = pdoc->CharAt(iChar); + tr->lpstrText[iPlace++] = pdoc->StyleAt(iChar); + } + tr->lpstrText[iPlace] = '\0'; + tr->lpstrText[iPlace + 1] = '\0'; + return iPlace; + } + + case SCI_CANREDO: + return (pdoc->CanRedo() && !pdoc->IsReadOnly()) ? 1 : 0; + + case SCI_MARKERLINEFROMHANDLE: + return pdoc->LineFromHandle(wParam); + + case SCI_MARKERDELETEHANDLE: + pdoc->DeleteMarkFromHandle(wParam); + break; + + case SCI_GETVIEWWS: + return vs.viewWhitespace; + + case SCI_SETVIEWWS: + vs.viewWhitespace = static_cast<WhiteSpaceVisibility>(wParam); + Redraw(); + break; + + case SCI_POSITIONFROMPOINT: + return PositionFromLocation(Point(wParam, lParam)); + + case SCI_POSITIONFROMPOINTCLOSE: + return PositionFromLocationClose(Point(wParam, lParam)); + + case SCI_GOTOLINE: + GoToLine(wParam); + break; + + case SCI_GOTOPOS: + SetEmptySelection(wParam); + EnsureCaretVisible(); + Redraw(); + break; + + case SCI_GETCURLINE: { + int lineCurrentPos = pdoc->LineFromPosition(currentPos); + int lineStart = pdoc->LineStart(lineCurrentPos); + unsigned int lineEnd = pdoc->LineStart(lineCurrentPos + 1); + if (lParam == 0) { + return 1 + lineEnd - lineStart; + } + PLATFORM_ASSERT(wParam > 0); + char *ptr = CharPtrFromSPtr(lParam); + unsigned int iPlace = 0; + for (unsigned int iChar = lineStart; iChar < lineEnd && iPlace < wParam - 1; iChar++) { + ptr[iPlace++] = pdoc->CharAt(iChar); + } + ptr[iPlace] = '\0'; + return currentPos - lineStart; + } + + case SCI_GETENDSTYLED: + return pdoc->GetEndStyled(); + + case SCI_GETEOLMODE: + return pdoc->eolMode; + + case SCI_SETEOLMODE: + pdoc->eolMode = wParam; + break; + + case SCI_STARTSTYLING: + pdoc->StartStyling(wParam, static_cast<char>(lParam)); + break; + + case SCI_SETSTYLING: + pdoc->SetStyleFor(wParam, static_cast<char>(lParam)); + break; + + case SCI_SETSTYLINGEX: // Specify a complete styling buffer + if (lParam == 0) + return 0; + pdoc->SetStyles(wParam, CharPtrFromSPtr(lParam)); + break; + + case SCI_SETBUFFEREDDRAW: + bufferedDraw = wParam != 0; + break; + + case SCI_GETBUFFEREDDRAW: + return bufferedDraw; + + case SCI_GETTWOPHASEDRAW: + return twoPhaseDraw; + + case SCI_SETTWOPHASEDRAW: + twoPhaseDraw = wParam != 0; + InvalidateStyleRedraw(); + break; + + case SCI_SETTABWIDTH: + if (wParam > 0) { + pdoc->tabInChars = wParam; + if (pdoc->indentInChars == 0) + pdoc->actualIndentInChars = pdoc->tabInChars; + } + InvalidateStyleRedraw(); + break; + + case SCI_GETTABWIDTH: + return pdoc->tabInChars; + + case SCI_SETINDENT: + pdoc->indentInChars = wParam; + if (pdoc->indentInChars != 0) + pdoc->actualIndentInChars = pdoc->indentInChars; + else + pdoc->actualIndentInChars = pdoc->tabInChars; + InvalidateStyleRedraw(); + break; + + case SCI_GETINDENT: + return pdoc->indentInChars; + + case SCI_SETUSETABS: + pdoc->useTabs = wParam != 0; + InvalidateStyleRedraw(); + break; + + case SCI_GETUSETABS: + return pdoc->useTabs; + + case SCI_SETLINEINDENTATION: + pdoc->SetLineIndentation(wParam, lParam); + break; + + case SCI_GETLINEINDENTATION: + return pdoc->GetLineIndentation(wParam); + + case SCI_GETLINEINDENTPOSITION: + return pdoc->GetLineIndentPosition(wParam); + + case SCI_SETTABINDENTS: + pdoc->tabIndents = wParam != 0; + break; + + case SCI_GETTABINDENTS: + return pdoc->tabIndents; + + case SCI_SETBACKSPACEUNINDENTS: + pdoc->backspaceUnindents = wParam != 0; + break; + + case SCI_GETBACKSPACEUNINDENTS: + return pdoc->backspaceUnindents; + + case SCI_SETMOUSEDWELLTIME: + dwellDelay = wParam; + ticksToDwell = dwellDelay; + break; + + case SCI_GETMOUSEDWELLTIME: + return dwellDelay; + + case SCI_WORDSTARTPOSITION: + return pdoc->ExtendWordSelect(wParam, -1, lParam != 0); + + case SCI_WORDENDPOSITION: + return pdoc->ExtendWordSelect(wParam, 1, lParam != 0); + + case SCI_SETWRAPMODE: + switch (wParam) { + case SC_WRAP_WORD: + wrapState = eWrapWord; + break; + case SC_WRAP_CHAR: + wrapState = eWrapChar; + break; + default: + wrapState = eWrapNone; + break; + } + xOffset = 0; + InvalidateStyleRedraw(); + ReconfigureScrollBars(); + break; + + case SCI_GETWRAPMODE: + return wrapState; + + case SCI_SETWRAPVISUALFLAGS: + wrapVisualFlags = wParam; + actualWrapVisualStartIndent = wrapVisualStartIndent; + if ((wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (actualWrapVisualStartIndent == 0)) + actualWrapVisualStartIndent = 1; // must indent to show start visual + InvalidateStyleRedraw(); + ReconfigureScrollBars(); + break; + + case SCI_GETWRAPVISUALFLAGS: + return wrapVisualFlags; + + case SCI_SETWRAPVISUALFLAGSLOCATION: + wrapVisualFlagsLocation = wParam; + InvalidateStyleRedraw(); + break; + + case SCI_GETWRAPVISUALFLAGSLOCATION: + return wrapVisualFlagsLocation; + + case SCI_SETWRAPSTARTINDENT: + wrapVisualStartIndent = wParam; + actualWrapVisualStartIndent = wrapVisualStartIndent; + if ((wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (actualWrapVisualStartIndent == 0)) + actualWrapVisualStartIndent = 1; // must indent to show start visual + InvalidateStyleRedraw(); + ReconfigureScrollBars(); + break; + + case SCI_GETWRAPSTARTINDENT: + return wrapVisualStartIndent; + + case SCI_SETLAYOUTCACHE: + llc.SetLevel(wParam); + break; + + case SCI_GETLAYOUTCACHE: + return llc.GetLevel(); + + case SCI_SETSCROLLWIDTH: + PLATFORM_ASSERT(wParam > 0); + if ((wParam > 0) && (wParam != static_cast<unsigned int >(scrollWidth))) { + scrollWidth = wParam; + SetScrollBars(); + } + break; + + case SCI_GETSCROLLWIDTH: + return scrollWidth; + + case SCI_LINESJOIN: + LinesJoin(); + break; + + case SCI_LINESSPLIT: + LinesSplit(wParam); + break; + + case SCI_TEXTWIDTH: + PLATFORM_ASSERT(wParam <= STYLE_MAX); + PLATFORM_ASSERT(lParam); + return TextWidth(wParam, CharPtrFromSPtr(lParam)); + + case SCI_TEXTHEIGHT: + return vs.lineHeight; + + case SCI_SETENDATLASTLINE: + PLATFORM_ASSERT((wParam == 0) || (wParam == 1)); + if (endAtLastLine != (wParam != 0)) { + endAtLastLine = wParam != 0; + SetScrollBars(); + } + break; + + case SCI_GETENDATLASTLINE: + return endAtLastLine; + + case SCI_SETCARETSTICKY: + PLATFORM_ASSERT((wParam == 0) || (wParam == 1)); + if (caretSticky != (wParam != 0)) { + caretSticky = wParam != 0; + } + break; + + case SCI_GETCARETSTICKY: + return caretSticky; + + case SCI_TOGGLECARETSTICKY: + caretSticky = !caretSticky; + break; + + case SCI_GETCOLUMN: + return pdoc->GetColumn(wParam); + + case SCI_FINDCOLUMN: + return pdoc->FindColumn(wParam, lParam); + + case SCI_SETHSCROLLBAR : + if (horizontalScrollBarVisible != (wParam != 0)) { + horizontalScrollBarVisible = wParam != 0; + SetScrollBars(); + ReconfigureScrollBars(); + } + break; + + case SCI_GETHSCROLLBAR: + return horizontalScrollBarVisible; + + case SCI_SETVSCROLLBAR: + if (verticalScrollBarVisible != (wParam != 0)) { + verticalScrollBarVisible = wParam != 0; + SetScrollBars(); + ReconfigureScrollBars(); + } + break; + + case SCI_GETVSCROLLBAR: + return verticalScrollBarVisible; + + case SCI_SETINDENTATIONGUIDES: + vs.viewIndentationGuides = wParam != 0; + Redraw(); + break; + + case SCI_GETINDENTATIONGUIDES: + return vs.viewIndentationGuides; + + case SCI_SETHIGHLIGHTGUIDE: + if ((highlightGuideColumn != static_cast<int>(wParam)) || (wParam > 0)) { + highlightGuideColumn = wParam; + Redraw(); + } + break; + + case SCI_GETHIGHLIGHTGUIDE: + return highlightGuideColumn; + + case SCI_GETLINEENDPOSITION: + return pdoc->LineEnd(wParam); + + case SCI_SETCODEPAGE: + if (ValidCodePage(wParam)) { + pdoc->dbcsCodePage = wParam; + InvalidateStyleRedraw(); + } + break; + + case SCI_GETCODEPAGE: + return pdoc->dbcsCodePage; + + case SCI_SETUSEPALETTE: + palette.allowRealization = wParam != 0; + InvalidateStyleRedraw(); + break; + + case SCI_GETUSEPALETTE: + return palette.allowRealization; + + // Marker definition and setting + case SCI_MARKERDEFINE: + if (wParam <= MARKER_MAX) + vs.markers[wParam].markType = lParam; + InvalidateStyleData(); + RedrawSelMargin(); + break; + case SCI_MARKERSETFORE: + if (wParam <= MARKER_MAX) + vs.markers[wParam].fore.desired = ColourDesired(lParam); + InvalidateStyleData(); + RedrawSelMargin(); + break; + case SCI_MARKERSETBACK: + if (wParam <= MARKER_MAX) + vs.markers[wParam].back.desired = ColourDesired(lParam); + InvalidateStyleData(); + RedrawSelMargin(); + break; + case SCI_MARKERSETALPHA: + if (wParam <= MARKER_MAX) + vs.markers[wParam].alpha = lParam; + InvalidateStyleRedraw(); + break; + case SCI_MARKERADD: { + int markerID = pdoc->AddMark(wParam, lParam); + return markerID; + } + case SCI_MARKERADDSET: + if (lParam != 0) + pdoc->AddMarkSet(wParam, lParam); + break; + + case SCI_MARKERDELETE: + pdoc->DeleteMark(wParam, lParam); + break; + + case SCI_MARKERDELETEALL: + pdoc->DeleteAllMarks(static_cast<int>(wParam)); + break; + + case SCI_MARKERGET: + return pdoc->GetMark(wParam); + + case SCI_MARKERNEXT: { + int lt = pdoc->LinesTotal(); + for (int iLine = wParam; iLine < lt; iLine++) { + if ((pdoc->GetMark(iLine) & lParam) != 0) + return iLine; + } + } + return -1; + + case SCI_MARKERPREVIOUS: { + for (int iLine = wParam; iLine >= 0; iLine--) { + if ((pdoc->GetMark(iLine) & lParam) != 0) + return iLine; + } + } + return -1; + + case SCI_MARKERDEFINEPIXMAP: + if (wParam <= MARKER_MAX) { + vs.markers[wParam].SetXPM(CharPtrFromSPtr(lParam)); + }; + InvalidateStyleData(); + RedrawSelMargin(); + break; + + case SCI_SETMARGINTYPEN: + if (ValidMargin(wParam)) { + vs.ms[wParam].style = lParam; + InvalidateStyleRedraw(); + } + break; + + case SCI_GETMARGINTYPEN: + if (ValidMargin(wParam)) + return vs.ms[wParam].style; + else + return 0; + + case SCI_SETMARGINWIDTHN: + if (ValidMargin(wParam)) { + // Short-circuit if the width is unchanged, to avoid unnecessary redraw. + if (vs.ms[wParam].width != lParam) { + vs.ms[wParam].width = lParam; + InvalidateStyleRedraw(); + } + } + break; + + case SCI_GETMARGINWIDTHN: + if (ValidMargin(wParam)) + return vs.ms[wParam].width; + else + return 0; + + case SCI_SETMARGINMASKN: + if (ValidMargin(wParam)) { + vs.ms[wParam].mask = lParam; + InvalidateStyleRedraw(); + } + break; + + case SCI_GETMARGINMASKN: + if (ValidMargin(wParam)) + return vs.ms[wParam].mask; + else + return 0; + + case SCI_SETMARGINSENSITIVEN: + if (ValidMargin(wParam)) { + vs.ms[wParam].sensitive = lParam != 0; + InvalidateStyleRedraw(); + } + break; + + case SCI_GETMARGINSENSITIVEN: + if (ValidMargin(wParam)) + return vs.ms[wParam].sensitive ? 1 : 0; + else + return 0; + + case SCI_STYLECLEARALL: + vs.ClearStyles(); + InvalidateStyleRedraw(); + break; + + case SCI_STYLESETFORE: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].fore.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETBACK: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].back.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETBOLD: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].bold = lParam != 0; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETITALIC: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].italic = lParam != 0; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETEOLFILLED: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].eolFilled = lParam != 0; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETSIZE: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].size = lParam; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETFONT: + if (lParam == 0) + return 0; + if (wParam <= STYLE_MAX) { + vs.SetStyleFontName(wParam, CharPtrFromSPtr(lParam)); + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETUNDERLINE: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].underline = lParam != 0; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETCASE: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].caseForce = static_cast<Style::ecaseForced>(lParam); + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETCHARACTERSET: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].characterSet = lParam; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETVISIBLE: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].visible = lParam != 0; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETCHANGEABLE: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].changeable = lParam != 0; + InvalidateStyleRedraw(); + } + break; + case SCI_STYLESETHOTSPOT: + if (wParam <= STYLE_MAX) { + vs.styles[wParam].hotspot = lParam != 0; + InvalidateStyleRedraw(); + } + break; + + case SCI_STYLERESETDEFAULT: + vs.ResetDefaultStyle(); + InvalidateStyleRedraw(); + break; + case SCI_SETSTYLEBITS: + pdoc->SetStylingBits(wParam); + break; + + case SCI_GETSTYLEBITS: + return pdoc->stylingBits; + + case SCI_SETLINESTATE: + return pdoc->SetLineState(wParam, lParam); + + case SCI_GETLINESTATE: + return pdoc->GetLineState(wParam); + + case SCI_GETMAXLINESTATE: + return pdoc->GetMaxLineState(); + + case SCI_GETCARETLINEVISIBLE: + return vs.showCaretLineBackground; + case SCI_SETCARETLINEVISIBLE: + vs.showCaretLineBackground = wParam != 0; + InvalidateStyleRedraw(); + break; + case SCI_GETCARETLINEBACK: + return vs.caretLineBackground.desired.AsLong(); + case SCI_SETCARETLINEBACK: + vs.caretLineBackground.desired = wParam; + InvalidateStyleRedraw(); + break; + case SCI_GETCARETLINEBACKALPHA: + return vs.caretLineAlpha; + case SCI_SETCARETLINEBACKALPHA: + vs.caretLineAlpha = wParam; + InvalidateStyleRedraw(); + break; + + // Folding messages + + case SCI_VISIBLEFROMDOCLINE: + return cs.DisplayFromDoc(wParam); + + case SCI_DOCLINEFROMVISIBLE: + return cs.DocFromDisplay(wParam); + + case SCI_WRAPCOUNT: + return WrapCount(wParam); + + case SCI_SETFOLDLEVEL: { + int prev = pdoc->SetLevel(wParam, lParam); + if (prev != lParam) + RedrawSelMargin(); + return prev; + } + + case SCI_GETFOLDLEVEL: + return pdoc->GetLevel(wParam); + + case SCI_GETLASTCHILD: + return pdoc->GetLastChild(wParam, lParam); + + case SCI_GETFOLDPARENT: + return pdoc->GetFoldParent(wParam); + + case SCI_SHOWLINES: + cs.SetVisible(wParam, lParam, true); + SetScrollBars(); + Redraw(); + break; + + case SCI_HIDELINES: + cs.SetVisible(wParam, lParam, false); + SetScrollBars(); + Redraw(); + break; + + case SCI_GETLINEVISIBLE: + return cs.GetVisible(wParam); + + case SCI_SETFOLDEXPANDED: + if (cs.SetExpanded(wParam, lParam != 0)) { + RedrawSelMargin(); + } + break; + + case SCI_GETFOLDEXPANDED: + return cs.GetExpanded(wParam); + + case SCI_SETFOLDFLAGS: + foldFlags = wParam; + Redraw(); + break; + + case SCI_TOGGLEFOLD: + ToggleContraction(wParam); + break; + + case SCI_ENSUREVISIBLE: + EnsureLineVisible(wParam, false); + break; + + case SCI_ENSUREVISIBLEENFORCEPOLICY: + EnsureLineVisible(wParam, true); + break; + + case SCI_SEARCHANCHOR: + SearchAnchor(); + break; + + case SCI_SEARCHNEXT: + case SCI_SEARCHPREV: + return SearchText(iMessage, wParam, lParam); + +#ifdef INCLUDE_DEPRECATED_FEATURES + case SCI_SETCARETPOLICY: // Deprecated + caretXPolicy = caretYPolicy = wParam; + caretXSlop = caretYSlop = lParam; + break; +#endif + + case SCI_SETXCARETPOLICY: + caretXPolicy = wParam; + caretXSlop = lParam; + break; + + case SCI_SETYCARETPOLICY: + caretYPolicy = wParam; + caretYSlop = lParam; + break; + + case SCI_SETVISIBLEPOLICY: + visiblePolicy = wParam; + visibleSlop = lParam; + break; + + case SCI_LINESONSCREEN: + return LinesOnScreen(); + + case SCI_SETSELFORE: + vs.selforeset = wParam != 0; + vs.selforeground.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETSELBACK: + vs.selbackset = wParam != 0; + vs.selbackground.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETSELALPHA: + vs.selAlpha = wParam; + InvalidateStyleRedraw(); + break; + + case SCI_GETSELALPHA: + return vs.selAlpha; + + case SCI_SETWHITESPACEFORE: + vs.whitespaceForegroundSet = wParam != 0; + vs.whitespaceForeground.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETWHITESPACEBACK: + vs.whitespaceBackgroundSet = wParam != 0; + vs.whitespaceBackground.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETCARETFORE: + vs.caretcolour.desired = ColourDesired(wParam); + InvalidateStyleRedraw(); + break; + + case SCI_GETCARETFORE: + return vs.caretcolour.desired.AsLong(); + + case SCI_SETCARETWIDTH: + if (wParam <= 0) + vs.caretWidth = 0; + else if (wParam >= 3) + vs.caretWidth = 3; + else + vs.caretWidth = wParam; + InvalidateStyleRedraw(); + break; + + case SCI_GETCARETWIDTH: + return vs.caretWidth; + + case SCI_ASSIGNCMDKEY: + kmap.AssignCmdKey(Platform::LowShortFromLong(wParam), + Platform::HighShortFromLong(wParam), lParam); + break; + + case SCI_CLEARCMDKEY: + kmap.AssignCmdKey(Platform::LowShortFromLong(wParam), + Platform::HighShortFromLong(wParam), SCI_NULL); + break; + + case SCI_CLEARALLCMDKEYS: + kmap.Clear(); + break; + + case SCI_INDICSETSTYLE: + if (wParam <= INDIC_MAX) { + vs.indicators[wParam].style = lParam; + InvalidateStyleRedraw(); + } + break; + + case SCI_INDICGETSTYLE: + return (wParam <= INDIC_MAX) ? vs.indicators[wParam].style : 0; + + case SCI_INDICSETFORE: + if (wParam <= INDIC_MAX) { + vs.indicators[wParam].fore.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + } + break; + + case SCI_INDICGETFORE: + return (wParam <= INDIC_MAX) ? vs.indicators[wParam].fore.desired.AsLong() : 0; + + 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_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_NEWLINE: + case SCI_FORMFEED: + case SCI_VCHOME: + case SCI_VCHOMEEXTEND: + case SCI_VCHOMEWRAP: + case SCI_VCHOMEWRAPEXTEND: + case SCI_ZOOMIN: + case SCI_ZOOMOUT: + case SCI_DELWORDLEFT: + case SCI_DELWORDRIGHT: + 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_WORDPARTLEFT: + case SCI_WORDPARTLEFTEXTEND: + case SCI_WORDPARTRIGHT: + case SCI_WORDPARTRIGHTEXTEND: + case SCI_DELETEBACKNOTLINE: + case SCI_HOMEDISPLAY: + case SCI_HOMEDISPLAYEXTEND: + case SCI_LINEENDDISPLAY: + case SCI_LINEENDDISPLAYEXTEND: + 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: + return KeyCommand(iMessage); + + case SCI_BRACEHIGHLIGHT: + SetBraceHighlight(static_cast<int>(wParam), lParam, STYLE_BRACELIGHT); + break; + + case SCI_BRACEBADLIGHT: + SetBraceHighlight(static_cast<int>(wParam), -1, STYLE_BRACEBAD); + break; + + case SCI_BRACEMATCH: + // wParam is position of char to find brace for, + // lParam is maximum amount of text to restyle to find it + return pdoc->BraceMatch(wParam, lParam); + + case SCI_GETVIEWEOL: + return vs.viewEOL; + + case SCI_SETVIEWEOL: + vs.viewEOL = wParam != 0; + InvalidateStyleRedraw(); + break; + + case SCI_SETZOOM: + vs.zoomLevel = wParam; + InvalidateStyleRedraw(); + NotifyZoom(); + break; + + case SCI_GETZOOM: + return vs.zoomLevel; + + case SCI_GETEDGECOLUMN: + return theEdge; + + case SCI_SETEDGECOLUMN: + theEdge = wParam; + InvalidateStyleRedraw(); + break; + + case SCI_GETEDGEMODE: + return vs.edgeState; + + case SCI_SETEDGEMODE: + vs.edgeState = wParam; + InvalidateStyleRedraw(); + break; + + case SCI_GETEDGECOLOUR: + return vs.edgecolour.desired.AsLong(); + + case SCI_SETEDGECOLOUR: + vs.edgecolour.desired = ColourDesired(wParam); + InvalidateStyleRedraw(); + break; + + case SCI_GETDOCPOINTER: + return reinterpret_cast<sptr_t>(pdoc); + + case SCI_SETDOCPOINTER: + CancelModes(); + SetDocPointer(reinterpret_cast<Document *>(lParam)); + return 0; + + case SCI_CREATEDOCUMENT: { + Document *doc = new Document(); + if (doc) { + doc->AddRef(); + } + return reinterpret_cast<sptr_t>(doc); + } + + case SCI_ADDREFDOCUMENT: + (reinterpret_cast<Document *>(lParam))->AddRef(); + break; + + case SCI_RELEASEDOCUMENT: + (reinterpret_cast<Document *>(lParam))->Release(); + break; + + case SCI_SETMODEVENTMASK: + modEventMask = wParam; + return 0; + + case SCI_GETMODEVENTMASK: + return modEventMask; + + case SCI_CONVERTEOLS: + pdoc->ConvertLineEnds(wParam); + SetSelection(currentPos, anchor); // Ensure selection inside document + return 0; + + case SCI_SETLENGTHFORENCODE: + lengthForEncode = wParam; + return 0; + + case SCI_SELECTIONISRECTANGLE: + return selType == selRectangle ? 1 : 0; + + case SCI_SETSELECTIONMODE: { + switch (wParam) { + case SC_SEL_STREAM: + moveExtendsSelection = !moveExtendsSelection || (selType != selStream); + selType = selStream; + break; + case SC_SEL_RECTANGLE: + moveExtendsSelection = !moveExtendsSelection || (selType != selRectangle); + selType = selRectangle; + break; + case SC_SEL_LINES: + moveExtendsSelection = !moveExtendsSelection || (selType != selLines); + selType = selLines; + break; + default: + moveExtendsSelection = !moveExtendsSelection || (selType != selStream); + selType = selStream; + } + InvalidateSelection(currentPos, anchor); + } + case SCI_GETSELECTIONMODE: + switch (selType) { + case selStream: + return SC_SEL_STREAM; + case selRectangle: + return SC_SEL_RECTANGLE; + case selLines: + return SC_SEL_LINES; + default: // ?! + return SC_SEL_STREAM; + } + case SCI_GETLINESELSTARTPOSITION: { + SelectionLineIterator lineIterator(this); + lineIterator.SetAt(wParam); + return lineIterator.startPos; + } + case SCI_GETLINESELENDPOSITION: { + SelectionLineIterator lineIterator(this); + lineIterator.SetAt(wParam); + return lineIterator.endPos; + } + + case SCI_SETOVERTYPE: + inOverstrike = wParam != 0; + break; + + case SCI_GETOVERTYPE: + return inOverstrike ? 1 : 0; + + case SCI_SETFOCUS: + SetFocusState(wParam != 0); + break; + + case SCI_GETFOCUS: + return hasFocus; + + case SCI_SETSTATUS: + errorStatus = wParam; + break; + + case SCI_GETSTATUS: + return errorStatus; + + case SCI_SETMOUSEDOWNCAPTURES: + mouseDownCaptures = wParam != 0; + break; + + case SCI_GETMOUSEDOWNCAPTURES: + return mouseDownCaptures; + + case SCI_SETCURSOR: + cursorMode = wParam; + DisplayCursor(Window::cursorText); + break; + + case SCI_GETCURSOR: + return cursorMode; + + case SCI_SETCONTROLCHARSYMBOL: + controlCharSymbol = wParam; + break; + + case SCI_GETCONTROLCHARSYMBOL: + return controlCharSymbol; + + case SCI_STARTRECORD: + recordingMacro = true; + return 0; + + case SCI_STOPRECORD: + recordingMacro = false; + return 0; + + case SCI_MOVECARETINSIDEVIEW: + MoveCaretInsideView(); + break; + + case SCI_SETFOLDMARGINCOLOUR: + vs.foldmarginColourSet = wParam != 0; + vs.foldmarginColour.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETFOLDMARGINHICOLOUR: + vs.foldmarginHighlightColourSet = wParam != 0; + vs.foldmarginHighlightColour.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETHOTSPOTACTIVEFORE: + vs.hotspotForegroundSet = wParam != 0; + vs.hotspotForeground.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETHOTSPOTACTIVEBACK: + vs.hotspotBackgroundSet = wParam != 0; + vs.hotspotBackground.desired = ColourDesired(lParam); + InvalidateStyleRedraw(); + break; + + case SCI_SETHOTSPOTACTIVEUNDERLINE: + vs.hotspotUnderline = wParam != 0; + InvalidateStyleRedraw(); + break; + + case SCI_SETHOTSPOTSINGLELINE: + vs.hotspotSingleLine = wParam != 0; + InvalidateStyleRedraw(); + break; + + case SCI_SETPASTECONVERTENDINGS: + convertPastes = wParam != 0; + break; + + case SCI_GETPASTECONVERTENDINGS: + return convertPastes ? 1 : 0; + + default: + return DefWndProc(iMessage, wParam, lParam); + } + //Platform::DebugPrintf("end wnd proc\n"); + return 0l; +} |