diff options
author | nyamatongwe <unknown> | 2000-03-08 01:38:23 +0000 |
---|---|---|
committer | nyamatongwe <unknown> | 2000-03-08 01:38:23 +0000 |
commit | 7fbd8e2a34d2f5084ce26ad95d7c70ae4de6a233 (patch) | |
tree | 83eec8af8ccee95c3f5a3f4185c1ae2cda3cd4db /src | |
parent | 8eba2a95b6aa25489c28eabfcd54e0389de78899 (diff) | |
download | scintilla-mirror-7fbd8e2a34d2f5084ce26ad95d7c70ae4de6a233.tar.gz |
Initial revision
Diffstat (limited to 'src')
-rw-r--r-- | src/Accessor.cxx | 112 | ||||
-rw-r--r-- | src/AutoComplete.cxx | 104 | ||||
-rw-r--r-- | src/AutoComplete.h | 43 | ||||
-rw-r--r-- | src/CallTip.cxx | 168 | ||||
-rw-r--r-- | src/CallTip.h | 46 | ||||
-rw-r--r-- | src/CellBuffer.cxx | 950 | ||||
-rw-r--r-- | src/CellBuffer.h | 197 | ||||
-rw-r--r-- | src/ContractionState.cxx | 203 | ||||
-rw-r--r-- | src/ContractionState.h | 50 | ||||
-rw-r--r-- | src/Document.cxx | 734 | ||||
-rw-r--r-- | src/Document.h | 222 |
11 files changed, 2829 insertions, 0 deletions
diff --git a/src/Accessor.cxx b/src/Accessor.cxx new file mode 100644 index 000000000..57b7e4dc4 --- /dev/null +++ b/src/Accessor.cxx @@ -0,0 +1,112 @@ +// SciTE - Scintilla based Text Editor +// Accessor.cxx - rapid easy access to contents of a Scintilla +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdlib.h> +#include <stdio.h> + +#include "Platform.h" + +#include "PropSet.h" +#include "Accessor.h" +#include "Scintilla.h" + +void Accessor::Fill(int position) { + if (lenDoc == -1) + lenDoc = Platform::SendScintilla(id, WM_GETTEXTLENGTH, 0, 0); + startPos = position - slopSize; + if (startPos + bufferSize > lenDoc) + startPos = lenDoc - bufferSize; + if (startPos < 0) + startPos = 0; + endPos = startPos + bufferSize; + if (endPos > lenDoc) + endPos = lenDoc; + + TEXTRANGE tr = {{startPos, endPos}, buf}; + Platform::SendScintilla(id, EM_GETTEXTRANGE, 0, reinterpret_cast<LPARAM>(&tr)); +} + +char Accessor::StyleAt(int position) { + return static_cast<char>(Platform::SendScintilla( + id, SCI_GETSTYLEAT, position, 0)); +} + +int Accessor::GetLine(int position) { + return Platform::SendScintilla(id, EM_LINEFROMCHAR, position, 0); +} + +int Accessor::LineStart(int line) { + return Platform::SendScintilla(id, EM_LINEINDEX, line, 0); +} + +int Accessor::LevelAt(int line) { + return Platform::SendScintilla(id, SCI_GETFOLDLEVEL, line, 0); +} + +int Accessor::Length() { + if (lenDoc == -1) + lenDoc = Platform::SendScintilla(id, WM_GETTEXTLENGTH, 0, 0); + return lenDoc; +} + +int Accessor::GetLineState(int line) { + return Platform::SendScintilla(id, SCI_GETLINESTATE, line); +} + +int Accessor::SetLineState(int line, int state) { + return Platform::SendScintilla(id, SCI_SETLINESTATE, line, state); +} + +void StylingContext::StartAt(unsigned int start, char chMask) { + Platform::SendScintilla(id, SCI_STARTSTYLING, start, chMask); +} + +void StylingContext::ColourSegment(unsigned int start, unsigned int end, int chAttr) { + // Only perform styling if non empty range + if (end != start - 1) { + if (end < start) { + Platform::DebugPrintf("Bad colour positions %d - %d\n", start, end); + } + + if (validLen + (end - start + 1) >= bufferSize) + Flush(); + if (validLen + (end - start + 1) >= bufferSize) { + // Too big for buffer so send directly + Platform::SendScintilla(id, SCI_SETSTYLING, end - start + 1, chAttr); + } else { + if (chAttr != chWhile) + chFlags = 0; + chAttr |= chFlags; + for (unsigned int i = start; i <= end; i++) { + styleBuf[validLen++] = chAttr; + } + } + } +} + +void StylingContext::StartSegment(unsigned int pos) { + startSeg = pos; +} + +void StylingContext::ColourTo(unsigned int pos, int chAttr) { + ColourSegment(startSeg, pos, chAttr); + startSeg = pos+1; +} + +int StylingContext::GetLine(int position) { + return Platform::SendScintilla(id, EM_LINEFROMCHAR, position, 0); +} + +void StylingContext::SetLevel(int line, int level) { + Platform::SendScintilla(id, SCI_SETFOLDLEVEL, line, level); +} + +void StylingContext::Flush() { + if (validLen > 0) { + Platform::SendScintilla(id, SCI_SETSTYLINGEX, validLen, + reinterpret_cast<LPARAM>(styleBuf)); + validLen = 0; + } +} diff --git a/src/AutoComplete.cxx b/src/AutoComplete.cxx new file mode 100644 index 000000000..c3ec29c3c --- /dev/null +++ b/src/AutoComplete.cxx @@ -0,0 +1,104 @@ +// Scintilla source code edit control +// AutoComplete.cxx - defines the auto completion list box +// Copyright 1998-2000 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 "Platform.h" + +#include "AutoComplete.h" + +AutoComplete::AutoComplete() { + lb = 0; + active = false; + posStart = 0; + strcpy(stopChars, ""); +} + +AutoComplete::~AutoComplete() { + lb.Destroy(); +} + +bool AutoComplete::Active() { + return active; +} + +void AutoComplete::Start(Window &parent, int ctrlID, int position, int startLen_) { + if (!lb.Created()) { + lb.Create(parent, ctrlID); + } + lb.Clear(); + active = true; + startLen = startLen_; + posStart = position; +} + +void AutoComplete::SetStopChars(const char *stopChars_) { + strncpy(stopChars, stopChars_, sizeof(stopChars)); + stopChars[sizeof(stopChars) - 1] = '\0'; +} + +bool AutoComplete::IsStopChar(char ch) { + return ch && strchr(stopChars, ch); +} + +int AutoComplete::SetList(const char *list) { + int maxStrLen = 12; + lb.Clear(); + char *words = new char[strlen(list) + 1]; + if (words) { + strcpy(words, list); + char *startword = words; + int i = 0; + for (; words && words[i]; i++) { + if (words[i] == ' ') { + words[i] = '\0'; + lb.Append(startword); + maxStrLen = Platform::Maximum(maxStrLen, strlen(startword)); + startword = words + i + 1; + } + } + if (startword) { + lb.Append(startword); + maxStrLen = Platform::Maximum(maxStrLen, strlen(startword)); + } + delete []words; + } + lb.Sort(); + return maxStrLen; +} + +void AutoComplete::Show() { + lb.Show(); + lb.Select(0); +} + +void AutoComplete::Cancel() { + if (lb.Created()) { + lb.Destroy(); + lb = 0; + active = false; + } +} + + +void AutoComplete::Move(int delta) { + int count = lb.Length(); + int current = lb.GetSelection(); + current += delta; + if (current >= count) + current = count - 1; + if (current < 0) + current = 0; + lb.Select(current); +} + +void AutoComplete::Select(const char *word) { + int pos = lb.Find(word); + //Platform::DebugPrintf("Autocompleting at <%s> %d\n", wordCurrent, pos); + if (pos != -1) + lb.Select(pos); +} + diff --git a/src/AutoComplete.h b/src/AutoComplete.h new file mode 100644 index 000000000..10216027b --- /dev/null +++ b/src/AutoComplete.h @@ -0,0 +1,43 @@ +// Scintilla source code edit control +// AutoComplete.h - defines the auto completion list box +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef AUTOCOMPLETE_H +#define AUTOCOMPLETE_H + +class AutoComplete { + bool active; + char stopChars[256]; +public: + ListBox lb; + int posStart; + int startLen; + + AutoComplete(); + ~AutoComplete(); + + // Is the auto completion list displayed? + bool Active(); + + // Display the auto completion list positioned to be near a character position + void Start(Window &parent, int ctrlID, int position, int startLen_); + + // The stop chars are characters which, when typed, cause the auto completion list to disappear + void SetStopChars(const char *stopChars_); + bool IsStopChar(char ch); + + // The list string contains a sequence of words separated by spaces + int SetList(const char *list); + + void Show(); + void Cancel(); + + // Move the current list element by delta, scrolling appropriately + void Move(int delta); + + // Select a list element that starts with word as the current element + void Select(const char *word); +}; + +#endif diff --git a/src/CallTip.cxx b/src/CallTip.cxx new file mode 100644 index 000000000..ad6740208 --- /dev/null +++ b/src/CallTip.cxx @@ -0,0 +1,168 @@ +// Scintilla source code edit control +// CallTip.cxx - code for displaying call tips +// Copyright 1998-2000 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 "Platform.h" + +#include "CallTip.h" + +CallTip::CallTip() { + wCallTip = 0; + inCallTipMode = false; + posStartCallTip = 0; + val = 0; + startHighlight = 0; + endHighlight = 0; + + colourBG.desired = Colour(0xff, 0xff, 0xff); + colourUnSel.desired = Colour(0x80, 0x80, 0x80); + colourSel.desired = Colour(0, 0, 0x80); + colourShade.desired = Colour(0, 0, 0); + colourLight.desired = Colour(0xc0, 0xc0, 0xc0); +} + +CallTip::~CallTip() { + wCallTip.Destroy(); + delete []val; + val = 0; +} + +void CallTip::RefreshColourPalette(Palette &pal, bool want) { + pal.WantFind(colourBG, want); + pal.WantFind(colourUnSel, want); + pal.WantFind(colourSel, want); + pal.WantFind(colourShade, want); + pal.WantFind(colourLight, want); +} + +void CallTip::PaintCT(Surface *surfaceWindow) { + if (!val) + return; + PRectangle rcClientPos = wCallTip.GetClientPosition(); + PRectangle rcClientSize(0, 0, rcClientPos.right - rcClientPos.left, + rcClientPos.bottom - rcClientPos.top); + PRectangle rcClient(1, 1, rcClientSize.right - 1, rcClientSize.bottom - 1); + + surfaceWindow->FillRectangle(rcClient, colourBG.allocated); + // To make a nice small call tip window, it is only sized to fit most normal characters without accents + int lineHeight = surfaceWindow->Height(font); + int ascent = surfaceWindow->Ascent(font) - surfaceWindow->InternalLeading(font); + + // For each line... + // Draw the definition in three parts: before highlight, highlighted, after highlight + int ytext = rcClient.top + ascent + 1; + char *chunkVal = val; + bool moreChunks = true; + while (moreChunks) { + char *chunkEnd = strchr(chunkVal, '\n'); + if (chunkEnd == NULL) { + chunkEnd = chunkVal + strlen(chunkVal); + moreChunks = false; + } + int chunkOffset = chunkVal - val; + int chunkLength = chunkEnd - chunkVal; + int chunkEndOffset = chunkOffset + chunkLength; + int thisStartHighlight = Platform::Maximum(startHighlight, chunkOffset); + thisStartHighlight = Platform::Minimum(thisStartHighlight, chunkEndOffset); + thisStartHighlight -= chunkOffset; + int thisEndHighlight = Platform::Maximum(endHighlight, chunkOffset); + thisEndHighlight = Platform::Minimum(thisEndHighlight, chunkEndOffset); + thisEndHighlight -= chunkOffset; + int x = 5; + int xEnd = x + surfaceWindow->WidthText(font, chunkVal, thisStartHighlight); + rcClient.left = x; + rcClient.top = ytext - ascent - 1; + rcClient.right = xEnd; + surfaceWindow->DrawText(rcClient, font, ytext, + chunkVal, thisStartHighlight, + colourUnSel.allocated, colourBG.allocated); + x = xEnd; + + xEnd = x + surfaceWindow->WidthText(font, chunkVal + thisStartHighlight, + thisEndHighlight - thisStartHighlight); + rcClient.top = ytext; + rcClient.left = x; + rcClient.right = xEnd; + surfaceWindow->DrawText(rcClient, font, ytext, + chunkVal + thisStartHighlight, thisEndHighlight - thisStartHighlight, + colourSel.allocated, colourBG.allocated); + x = xEnd; + + xEnd = x + surfaceWindow->WidthText(font, chunkVal + thisEndHighlight, + chunkLength - thisEndHighlight); + rcClient.left = x; + rcClient.right = xEnd; + surfaceWindow->DrawText(rcClient, font, ytext, + chunkVal + thisEndHighlight, chunkLength - thisEndHighlight, + colourUnSel.allocated, colourBG.allocated); + chunkVal = chunkEnd + 1; + ytext += lineHeight; + } + // Draw a raised border around the edges of the window + surfaceWindow->MoveTo(0, rcClientSize.bottom - 1); + surfaceWindow->PenColour(colourShade.allocated); + surfaceWindow->LineTo(rcClientSize.right - 1, rcClientSize.bottom - 1); + surfaceWindow->LineTo(rcClientSize.right - 1, 0); + surfaceWindow->PenColour(colourLight.allocated); + surfaceWindow->LineTo(0, 0); + surfaceWindow->LineTo(0, rcClientSize.bottom - 1); +} + +PRectangle CallTip::CallTipStart(int pos, Point pt, const char *defn, + const char *faceName, int size) { + Surface surfaceMeasure; + surfaceMeasure.Init(); + int deviceHeight = (size * surfaceMeasure.LogPixelsY()) / 72; + font.Create(faceName, deviceHeight); + if (val) + delete []val; + val = new char[strlen(defn) + 1]; + if (!val) + return PRectangle(); + strcpy(val, defn); + startHighlight = 0; + endHighlight = 0; + inCallTipMode = true; + posStartCallTip = pos; + // Look for multiple lines in the text + // Only support \n here - simply means container must avoid \r! + int width = 0; + int numLines = 1; + const char *newline; + const char *look = val; + while ((newline = strchr(look, '\n')) != NULL) { + int thisWidth = surfaceMeasure.WidthText(font, look, newline - look); + width = Platform::Maximum(width, thisWidth); + look = newline + 1; + numLines++; + } + int lastWidth = surfaceMeasure.WidthText(font, look, strlen(look)); + width = Platform::Maximum(width, lastWidth) + 10; + int lineHeight = surfaceMeasure.Height(font); + // Extra line for border and an empty line at top and bottom + int height = lineHeight * numLines - surfaceMeasure.InternalLeading(font) + 2 + 2; + return PRectangle(pt.x -5, pt.y + lineHeight + 1, pt.x + width - 5, pt.y + lineHeight + 1 + height); +} + + +void CallTip::CallTipCancel() { + inCallTipMode = false; + if (wCallTip.Created()) { + wCallTip.Destroy(); + } +} + +void CallTip::SetHighlight(int start, int end) { + // Avoid flashing by checking something has really changed + if ((start != startHighlight) || (end != endHighlight)) { + startHighlight = start; + endHighlight = end; + if (wCallTip.Created()) { + wCallTip.InvalidateAll(); + } + } +} diff --git a/src/CallTip.h b/src/CallTip.h new file mode 100644 index 000000000..cd5b093c8 --- /dev/null +++ b/src/CallTip.h @@ -0,0 +1,46 @@ +// Scintilla source code edit control +// CallTip.h - interface to the call tip control +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef CALLTIP_H +#define CALLTIP_H + +const char callClassName[] = "CallTip"; + +class CallTip { + int startHighlight; + int endHighlight; + char *val; + Font font; +public: + Window wCallTip; + Window wDraw; + bool inCallTipMode; + int posStartCallTip; + ColourPair colourBG; + ColourPair colourUnSel; + ColourPair colourSel; + ColourPair colourShade; + ColourPair colourLight; + + CallTip(); + ~CallTip(); + + // Claim or accept palette entries for the colours required to paint a calltip + void RefreshColourPalette(Palette &pal, bool want); + + void PaintCT(Surface *surfaceWindow); + + // Setup the calltip and return a rectangle of the area required + PRectangle CallTipStart(int pos, Point pt, const char *defn, + const char *faceName, int size); + + void CallTipCancel(); + + // Set a range of characters to be displayed in a highlight style. + // Commonly used to highlight the current parameter. + void SetHighlight(int start, int end); +}; + +#endif diff --git a/src/CellBuffer.cxx b/src/CellBuffer.cxx new file mode 100644 index 000000000..777688511 --- /dev/null +++ b/src/CellBuffer.cxx @@ -0,0 +1,950 @@ +// Scintilla source code edit control +// CellBuffer.cxx - manages a buffer of cells +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "Platform.h" + +#include "Scintilla.h" +#include "SVector.h" +#include "CellBuffer.h" + +MarkerHandleSet::MarkerHandleSet() { + root = 0; +} + +MarkerHandleSet::~MarkerHandleSet() { + MarkerHandleNumber *mhn = root; + while (mhn) { + MarkerHandleNumber *mhnToFree = mhn; + mhn = mhn->next; + delete mhnToFree; + } + root = 0; +} + +int MarkerHandleSet::Length() { + int c = 0; + MarkerHandleNumber *mhn = root; + while (mhn) { + c++; + mhn = mhn->next; + } + return c; +} + +int MarkerHandleSet::NumberFromHandle(int handle) { + MarkerHandleNumber *mhn = root; + while (mhn) { + if (mhn->handle == handle) { + return mhn->number; + } + mhn = mhn->next; + } + return - 1; +} + +int MarkerHandleSet::MarkValue() { + unsigned int m = 0; + MarkerHandleNumber *mhn = root; + while (mhn) { + m |= (1 << mhn->number); + mhn = mhn->next; + } + return m; +} + +bool MarkerHandleSet::Contains(int handle) { + MarkerHandleNumber *mhn = root; + while (mhn) { + if (mhn->handle == handle) { + return true; + } + mhn = mhn->next; + } + return false; +} + +bool MarkerHandleSet::InsertHandle(int handle, int markerNum) { + MarkerHandleNumber *mhn = new MarkerHandleNumber; + if (!mhn) + return false; + mhn->handle = handle; + mhn->number = markerNum; + mhn->next = root; + root = mhn; + return true; +} + +void MarkerHandleSet::RemoveHandle(int handle) { + MarkerHandleNumber **pmhn = &root; + while (*pmhn) { + MarkerHandleNumber *mhn = *pmhn; + if (mhn->handle == handle) { + *pmhn = mhn->next; + delete mhn; + return; + } + pmhn = &((*pmhn)->next); + } +} + +void MarkerHandleSet::RemoveNumber(int markerNum) { + MarkerHandleNumber **pmhn = &root; + while (*pmhn) { + MarkerHandleNumber *mhn = *pmhn; + if (mhn->number == markerNum) { + *pmhn = mhn->next; + delete mhn; + return; + } + pmhn = &((*pmhn)->next); + } +} + +void MarkerHandleSet::CombineWith(MarkerHandleSet *other) { + MarkerHandleNumber **pmhn = &root; + while (*pmhn) { + pmhn = &((*pmhn)->next); + } + *pmhn = other->root; + other->root = 0; +} + +LineVector::LineVector() { + linesData = 0; + lines = 0; + levels = 0; + Init(); +} + +LineVector::~LineVector() { + for (int line = 0; line < lines; line++) { + delete linesData[line].handleSet; + linesData[line].handleSet = 0; + } + delete []linesData; + linesData = 0; + delete []levels; + levels = 0; +} + +void LineVector::Init() { + for (int line = 0; line < lines; line++) { + delete linesData[line].handleSet; + linesData[line].handleSet = 0; + } + delete []linesData; + linesData = new LineData[static_cast<int>(growSize)]; + size = growSize; + lines = 1; + delete []levels; + levels = 0; + sizeLevels = 0; +} + +void LineVector::Expand(int sizeNew) { + LineData *linesDataNew = new LineData[sizeNew]; + if (linesDataNew) { + for (int i = 0; i < size; i++) + linesDataNew[i] = linesData[i]; + // Do not delete handleSets here as they are transferred to new linesData + delete []linesData; + linesData = linesDataNew; + size = sizeNew; + } else { + Platform::DebugPrintf("No memory available\n"); + // TODO: Blow up + } +} + +void LineVector::ExpandLevels(int sizeNew) { + if (sizeNew == -1) + sizeNew = size; + int *levelsNew = new int[sizeNew]; + if (levelsNew) { + int i = 0; + for (; i < sizeLevels; i++) + levelsNew[i] = levels[i]; + for (; i < sizeNew; i++) + levelsNew[i] = SC_FOLDLEVELBASE; + delete []levels; + levels = levelsNew; + sizeLevels = sizeNew; + } else { + Platform::DebugPrintf("No memory available\n"); + // TODO: Blow up + } +} + +void LineVector::InsertValue(int pos, int value) { + //Platform::DebugPrintf("InsertValue[%d] = %d\n", pos, value); + if ((lines + 2) >= size) { + Expand(size + growSize); + if (levels) { + ExpandLevels(size + growSize); + } + } + lines++; + for (int i = lines + 1; i > pos; i--) { + linesData[i] = linesData[i - 1]; + } + linesData[pos].startPosition = value; + linesData[pos].handleSet = 0; +} + +void LineVector::SetValue(int pos, int value) { + //Platform::DebugPrintf("SetValue[%d] = %d\n", pos, value); + if ((pos + 2) >= size) { + //Platform::DebugPrintf("Resize %d %d\n", size,pos); + Expand(pos + growSize); + //Platform::DebugPrintf("end Resize %d %d\n", size,pos); + lines = pos; + if (levels) { + ExpandLevels(pos + growSize); + } + } + linesData[pos].startPosition = value; +} + +void LineVector::Remove(int pos) { + //Platform::DebugPrintf("Remove %d\n", pos); + // Retain the markers from the deleted line by oring them into the previous line + if (pos > 0) { + MergeMarkers(pos - 1); + } + for (int i = pos; i < lines; i++) { + linesData[i] = linesData[i + 1]; + } + lines--; +} + +int LineVector::LineFromPosition(int pos) { + //Platform::DebugPrintf("LineFromPostion %d lines=%d end = %d\n", pos, lines, linesData[lines].startPosition); + if (lines == 0) + return 0; + //Platform::DebugPrintf("LineFromPosition %d\n", pos); + if (pos >= linesData[lines].startPosition) + return lines - 1; + int lower = 0; + int upper = lines; + int middle = 0; + do { + middle = (upper + lower + 1) / 2; // Round high + if (pos < linesData[middle].startPosition) { + upper = middle - 1; + } else { + lower = middle; + } + } while (lower < upper); + //Platform::DebugPrintf("LineFromPostion %d %d %d\n", pos, lower, linesData[lower].startPosition, linesData[lower > 1 ? lower - 1 : 0].startPosition); + return lower; +} + +int LineVector::AddMark(int line, int markerNum) { + handleCurrent++; + if (!linesData[line].handleSet) { + // Need new structure to hold marker handle + linesData[line].handleSet = new MarkerHandleSet; + if (!linesData[line].handleSet) + return - 1; + } + linesData[line].handleSet->InsertHandle(handleCurrent, markerNum); + + return handleCurrent; +} + +void LineVector::MergeMarkers(int pos) { + if (linesData[pos].handleSet || linesData[pos + 1].handleSet) { + if (linesData[pos].handleSet && linesData[pos + 1].handleSet) { + linesData[pos].handleSet->CombineWith(linesData[pos].handleSet); + linesData[pos].handleSet = 0; + } + } +} + +void LineVector::DeleteMark(int line, int markerNum) { + if (linesData[line].handleSet) { + if (markerNum == -1) { + delete linesData[line].handleSet; + linesData[line].handleSet = 0; + } else { + linesData[line].handleSet->RemoveNumber(markerNum); + if (linesData[line].handleSet->Length() == 0) { + delete linesData[line].handleSet; + linesData[line].handleSet = 0; + } + } + } +} + +void LineVector::DeleteMarkFromHandle(int markerHandle) { + int line = LineFromHandle(markerHandle); + if (line >= 0) { + linesData[line].handleSet->RemoveHandle(markerHandle); + if (linesData[line].handleSet->Length() == 0) { + delete linesData[line].handleSet; + linesData[line].handleSet = 0; + } + } +} + +int LineVector::LineFromHandle(int markerHandle) { + for (int line = 0; line < lines; line++) { + if (linesData[line].handleSet) { + if (linesData[line].handleSet->Contains(markerHandle)) { + return line; + } + } + } + return - 1; +} + +Action::Action() { + at = startAction; + position = 0; + data = 0; + lenData = 0; +} + +Action::~Action() { + Destroy(); +} + +void Action::Create(actionType at_, int position_, char *data_, int lenData_) { + delete []data; + position = position_; + at = at_; + data = data_; + lenData = lenData_; +} + +void Action::Destroy() { + delete []data; + data = 0; +} + +void Action::Grab(Action *source) { + delete []data; + + position = source->position; + at = source->at; + data = source->data; + lenData = source->lenData; + + // Ownership of source data transferred to this + source->position = 0; + source->at = startAction; + source->data = 0; + source->lenData = 0; +} + +CellBuffer::CellBuffer(int initialLength) { + body = new char[initialLength]; + size = initialLength; + length = 0; + part1len = 0; + gaplen = initialLength; + part2body = body + gaplen; + readOnly = false; + + lenActions = 100; + actions = new Action[lenActions]; + maxAction = 0; + currentAction = 0; + collectingUndo = undoCollectAutoStart; + undoSequenceDepth = 0; + savePoint = 0; + + actions[currentAction].Create(startAction); +} + +CellBuffer::~CellBuffer() { + delete []body; + body = 0; + delete []actions; + actions = 0; +} + +void CellBuffer::GapTo(int position) { + if (position == part1len) + return; + if (position < part1len) { + int diff = part1len - position; + //Platform::DebugPrintf("Move gap backwards to %d diff = %d part1len=%d length=%d \n", position,diff, part1len, length); + for (int i = 0; i < diff; i++) + body[part1len + gaplen - i - 1] = body[part1len - i - 1]; + } else { // position > part1len + int diff = position - part1len; + //Platform::DebugPrintf("Move gap forwards to %d diff =%d\n", position,diff); + for (int i = 0; i < diff; i++) + body[part1len + i] = body[part1len + gaplen + i]; + } + part1len = position; + part2body = body + gaplen; +} + +void CellBuffer::RoomFor(int insertionLength) { + //Platform::DebugPrintf("need room %d %d\n", gaplen, insertionLength); + if (gaplen <= insertionLength) { + //Platform::DebugPrintf("need room %d %d\n", gaplen, insertionLength); + GapTo(length); + int newSize = size + insertionLength + 4000; + //Platform::DebugPrintf("moved gap %d\n", newSize); + char *newBody = new char[newSize]; + memcpy(newBody, body, size); + delete []body; + body = newBody; + gaplen += newSize - size; + part2body = body + gaplen; + size = newSize; + //Platform::DebugPrintf("end need room %d %d - size=%d length=%d\n", gaplen, insertionLength,size,length); + } +} + +// To make it easier to write code that uses ByteAt, a position outside the range of the buffer +// can be retrieved. All characters outside the range have the value '\0'. +char CellBuffer::ByteAt(int position) { + if (position < part1len) { + if (position < 0) { + return '\0'; + } else { + return body[position]; + } + } else { + if (position >= length) { + return '\0'; + } else { + return part2body[position]; + } + } +} + +void CellBuffer::SetByteAt(int position, char ch) { + + if (position < 0) { + //Platform::DebugPrintf("Bad position %d\n",position); + return; + } + if (position >= length + 11) { + Platform::DebugPrintf("Very Bad position %d of %d\n", position, length); + //exit(2); + return; + } + if (position >= length) { + //Platform::DebugPrintf("Bad position %d of %d\n",position,length); + return; + } + + if (position < part1len) { + body[position] = ch; + } else { + part2body[position] = ch; + } +} + +char CellBuffer::CharAt(int position) { + return ByteAt(position*2); +} + +void CellBuffer::GetCharRange(char *buffer, int position, int lengthRetrieve) { + if (lengthRetrieve < 0) + return; + if (position < 0) + return; + int bytePos = position * 2; + if ((bytePos + lengthRetrieve * 2) > length) { + Platform::DebugPrintf("Bad GetCharRange %d for %d of %d\n",bytePos, + lengthRetrieve, length); + return; + } + GapTo(0); // Move the buffer so its easy to subscript into it + char *pb = part2body + bytePos; + while (lengthRetrieve--) { + *buffer++ = *pb; + pb +=2; + } +} + +char CellBuffer::StyleAt(int position) { + return ByteAt(position*2 + 1); +} + +const char *CellBuffer::InsertString(int position, char *s, int insertLength) { + char *data = 0; + // InsertString and DeleteChars are the bottleneck though which all changes occur + if (!readOnly) { + if (collectingUndo) { + // Save into the undo/redo stack, but only the characters - not the formatting + // This takes up about half load time + data = new char[insertLength / 2]; + for (int i = 0; i < insertLength / 2; i++) { + data[i] = s[i * 2]; + } + AppendAction(insertAction, position, data, insertLength / 2); + } + + BasicInsertString(position, s, insertLength); + } + return data; +} + +void CellBuffer::InsertCharStyle(int position, char ch, char style) { + char s[2]; + s[0] = ch; + s[1] = style; + InsertString(position*2, s, 2); +} + +bool CellBuffer::SetStyleAt(int position, char style, char mask) { + char curVal = ByteAt(position*2 + 1); + if ((curVal & mask) != style) { + SetByteAt(position*2 + 1, (curVal & ~mask) | style); + return true; + } else { + return false; + } +} + +bool CellBuffer::SetStyleFor(int position, int lengthStyle, char style, char mask) { + int bytePos = position * 2 + 1; + bool changed = false; + while (lengthStyle--) { + char curVal = ByteAt(bytePos); + if ((curVal & mask) != style) { + SetByteAt(bytePos, (curVal & ~mask) | style); + changed = true; + } + bytePos += 2; + } + return changed; +} + +void CellBuffer::EnsureUndoRoom() { + //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, length, currentAction); + if (currentAction >= 2) { + // Have to test that there is room for 2 more actions in the array + // as two actions may be created by this function + if (currentAction >= (lenActions - 2)) { + // Run out of undo nodes so extend the array + int lenActionsNew = lenActions * 2; + Action *actionsNew = new Action[lenActionsNew]; + if (!actionsNew) + return; + for (int act = 0; act <= currentAction; act++) + actionsNew[act].Grab(&actions[act]); + delete []actions; + lenActions = lenActionsNew; + actions = actionsNew; + } + } +} + +void CellBuffer::AppendAction(actionType at, int position, char *data, int lengthData) { + EnsureUndoRoom(); + //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction); + if (currentAction >= 2) { + // See if current action can be coalesced into previous action + // Will work if both are inserts or deletes and position is same or two different + if ((at != actions[currentAction - 1].at) || (abs(position - actions[currentAction - 1].position) > 2)) { + currentAction++; + } else if (currentAction == savePoint) { + currentAction++; + } + } else { + currentAction++; + } + actions[currentAction].Create(at, position, data, lengthData); + if ((collectingUndo == undoCollectAutoStart) && (0 == undoSequenceDepth)) { + currentAction++; + actions[currentAction].Create(startAction); + } + maxAction = currentAction; +} + +const char *CellBuffer::DeleteChars(int position, int deleteLength) { + // InsertString and DeleteChars are the bottleneck though which all changes occur + char *data = 0; + if (!readOnly) { + if (collectingUndo) { + // Save into the undo/redo stack, but only the characters - not the formatting + data = new char[deleteLength / 2]; + for (int i = 0; i < deleteLength / 2; i++) { + data[i] = ByteAt(position + i * 2); + } + AppendAction(removeAction, position, data, deleteLength / 2); + } + + BasicDeleteChars(position, deleteLength); + } + return data; +} + +int CellBuffer::ByteLength() { + return length; +} + +int CellBuffer::Length() { + return ByteLength() / 2; +} + +int CellBuffer::Lines() { + //Platform::DebugPrintf("Lines = %d\n", lv.lines); + return lv.lines; +} + +int CellBuffer::LineStart(int line) { + if (line < 0) + return 0; + else if (line > lv.lines) + return length; + else + return lv.linesData[line].startPosition; +} + +bool CellBuffer::IsReadOnly() { + return readOnly; +} + +void CellBuffer::SetReadOnly(bool set) { + readOnly = set; +} + +void CellBuffer::SetSavePoint() { + savePoint = currentAction; +} + +bool CellBuffer::IsSavePoint() { + return savePoint == currentAction; +} + +int CellBuffer::AddMark(int line, int markerNum) { + if ((line >= 0) && (line < lv.lines)) { + return lv.AddMark(line, markerNum); + } + return - 1; +} + +void CellBuffer::DeleteMark(int line, int markerNum) { + if ((line >= 0) && (line < lv.lines)) { + lv.DeleteMark(line, markerNum); + } +} + +void CellBuffer::DeleteMarkFromHandle(int markerHandle) { + lv.DeleteMarkFromHandle(markerHandle); +} + +int CellBuffer::GetMark(int line) { + if ((line >= 0) && (line < lv.lines) && (lv.linesData[line].handleSet)) + return lv.linesData[line].handleSet->MarkValue(); + return 0; +} + +void CellBuffer::DeleteAllMarks(int markerNum) { + for (int line = 0; line < lv.lines; line++) { + lv.DeleteMark(line, markerNum); + } +} + +int CellBuffer::LineFromHandle(int markerHandle) { + return lv.LineFromHandle(markerHandle); +} + +// Without undo + +void CellBuffer::BasicInsertString(int position, char *s, int insertLength) { + //Platform::DebugPrintf("Inserting at %d for %d\n", position, insertLength); + if (insertLength == 0) + return; + RoomFor(insertLength); + GapTo(position); + + memcpy(body + part1len, s, insertLength); + length += insertLength; + part1len += insertLength; + gaplen -= insertLength; + part2body = body + gaplen; + + int lineInsert = lv.LineFromPosition(position / 2) + 1; + // Point all the lines after the insertion point further along in the buffer + for (int lineAfter = lineInsert; lineAfter <= lv.lines; lineAfter++) { + lv.linesData[lineAfter].startPosition += insertLength / 2; + } + char chPrev = ' '; + if ((position - 2) >= 0) + chPrev = ByteAt(position - 2); + char chAfter = ' '; + if ((position + insertLength) < length) + chAfter = ByteAt(position + insertLength); + if (chPrev == '\r' && chAfter == '\n') { + //Platform::DebugPrintf("Splitting a crlf pair at %d\n", lineInsert); + // Splitting up a crlf pair at position + lv.InsertValue(lineInsert, position / 2); + lineInsert++; + } + char ch = ' '; + for (int i = 0; i < insertLength; i += 2) { + ch = s[i]; + if (ch == '\r') { + //Platform::DebugPrintf("Inserting cr at %d\n", lineInsert); + lv.InsertValue(lineInsert, (position + i) / 2 + 1); + lineInsert++; + } else if (ch == '\n') { + if (chPrev == '\r') { + //Platform::DebugPrintf("Patching cr before lf at %d\n", lineInsert-1); + // Patch up what was end of line + lv.SetValue(lineInsert - 1, (position + i) / 2 + 1); + } else { + //Platform::DebugPrintf("Inserting lf at %d\n", lineInsert); + lv.InsertValue(lineInsert, (position + i) / 2 + 1); + lineInsert++; + } + } + chPrev = ch; + } + // Joining two lines where last insertion is cr and following text starts with lf + if (chAfter == '\n') { + if (ch == '\r') { + //Platform::DebugPrintf("Joining cr before lf at %d\n", lineInsert-1); + // End of line already in buffer so drop the newly created one + lv.Remove(lineInsert - 1); + } + } +} + +void CellBuffer::BasicDeleteChars(int position, int deleteLength) { + //Platform::DebugPrintf("Deleting at %d for %d\n", position, deleteLength); + if (deleteLength == 0) + return; + + if ((position == 0) && (deleteLength == length)) { + // If whole buffer is being deleted, faster to reinitialise lines data + // than to delete each line. + //printf("Whole buffer being deleted\n"); + lv.Init(); + } else { + // Have to fix up line positions before doing deletion as looking at text in buffer + // to work out which lines have been removed + + int lineRemove = lv.LineFromPosition(position / 2) + 1; + // Point all the lines after the insertion point further along in the buffer + for (int lineAfter = lineRemove; lineAfter <= lv.lines; lineAfter++) { + lv.linesData[lineAfter].startPosition -= deleteLength / 2; + } + char chPrev = ' '; + if (position >= 2) + chPrev = ByteAt(position - 2); + char chBefore = chPrev; + char chNext = ' '; + if (position < length) + chNext = ByteAt(position); + bool ignoreNL = false; + if (chPrev == '\r' && chNext == '\n') { + //Platform::DebugPrintf("Deleting lf after cr, move line end to cr at %d\n", lineRemove); + // Move back one + lv.SetValue(lineRemove, position / 2); + lineRemove++; + ignoreNL = true; // First \n is not real deletion + } + + char ch = chNext; + for (int i = 0; i < deleteLength; i += 2) { + chNext = ' '; + if ((position + i + 2) < length) + chNext = ByteAt(position + i + 2); + //Platform::DebugPrintf("Deleting %d %x\n", i, ch); + if (ch == '\r') { + if (chNext != '\n') { + //Platform::DebugPrintf("Removing cr end of line\n"); + lv.Remove(lineRemove); + } + } else if ((ch == '\n') && !ignoreNL) { + //Platform::DebugPrintf("Removing lf end of line\n"); + lv.Remove(lineRemove); + ignoreNL = false; // Further \n are not real deletions + } + + ch = chNext; + } + // May have to fix up end if last deletion causes cr to be next to lf + // or removes one of a crlf pair + char chAfter = ' '; + if ((position + deleteLength) < length) + chAfter = ByteAt(position + deleteLength); + if (chBefore == '\r' && chAfter == '\n') { + //d.printf("Joining cr before lf at %d\n", lineRemove); + // Using lineRemove-1 as cr ended line before start of deletion + lv.Remove(lineRemove - 1); + lv.SetValue(lineRemove - 1, position / 2 + 1); + } + } + GapTo(position); + length -= deleteLength; + gaplen += deleteLength; + part2body = body + gaplen; +} + +undoCollectionType CellBuffer::SetUndoCollection(undoCollectionType collectUndo) { + collectingUndo = collectUndo; + undoSequenceDepth = 0; + return collectingUndo; +} + +bool CellBuffer::IsCollectingUndo() { + return collectingUndo; +} + +void CellBuffer::AppendUndoStartAction() { + EnsureUndoRoom(); + // Finish any currently active undo sequence + undoSequenceDepth = 0; + if (actions[currentAction].at != startAction) { + undoSequenceDepth++; + currentAction++; + actions[currentAction].Create(startAction); + maxAction = currentAction; + } +} + +void CellBuffer::BeginUndoAction() { + EnsureUndoRoom(); + if (undoSequenceDepth == 0) { + if (actions[currentAction].at != startAction) { + currentAction++; + actions[currentAction].Create(startAction); + maxAction = currentAction; + } + } + undoSequenceDepth++; +} + +void CellBuffer::EndUndoAction() { + EnsureUndoRoom(); + undoSequenceDepth--; + if (0 == undoSequenceDepth) { + if (actions[currentAction].at != startAction) { + currentAction++; + actions[currentAction].Create(startAction); + maxAction = currentAction; + } + } +} + +void CellBuffer::DeleteUndoHistory() { + for (int i = 1; i < maxAction; i++) + actions[i].Destroy(); + maxAction = 0; + currentAction = 0; + savePoint = 0; +} + +bool CellBuffer::CanUndo() { + return (!readOnly) && ((currentAction > 0) && (maxAction > 0)); +} + +int CellBuffer::StartUndo() { + // Drop any trailing startAction + if (actions[currentAction].at == startAction && currentAction > 0) + currentAction--; + + // Count the steps in this action + int act = currentAction; + while (actions[act].at != startAction && act > 0) { + act--; + } + return currentAction - act; +} + +const Action &CellBuffer::UndoStep() { + const Action &actionStep = actions[currentAction]; + if (actionStep.at == insertAction) { + BasicDeleteChars(actionStep.position, actionStep.lenData*2); + } else if (actionStep.at == removeAction) { + char *styledData = new char[actionStep.lenData * 2]; + for (int i = 0; i < actionStep.lenData; i++) { + styledData[i*2] = actionStep.data[i]; + styledData[i*2+1] = 0; + } + BasicInsertString(actionStep.position, styledData, actionStep.lenData*2); + delete []styledData; + } + currentAction--; + return actionStep; +} + +bool CellBuffer::CanRedo() { + return (!readOnly) && (maxAction > currentAction); +} + +int CellBuffer::StartRedo() { + // Drop any leading startAction + if (actions[currentAction].at == startAction && currentAction < maxAction) + currentAction++; + + // Count the steps in this action + int act = currentAction; + while (actions[act].at != startAction && act < maxAction) { + act++; + } + return act - currentAction; +} + +const Action &CellBuffer::RedoStep() { + const Action &actionStep = actions[currentAction]; + if (actionStep.at == insertAction) { + char *styledData = new char[actionStep.lenData * 2]; + for (int i = 0; i < actionStep.lenData; i++) { + styledData[i*2] = actionStep.data[i]; + styledData[i*2+1] = 0; + } + BasicInsertString(actionStep.position, styledData, actionStep.lenData*2); + delete []styledData; + } else if (actionStep.at == removeAction) { + BasicDeleteChars(actionStep.position, actionStep.lenData*2); + } + currentAction++; + return actionStep; +} + +int CellBuffer::SetLineState(int line, int state) { + int stateOld = lineStates[line]; + lineStates[line] = state; + return stateOld; +} + +int CellBuffer::GetLineState(int line) { + return lineStates[line]; +} + +int CellBuffer::GetMaxLineState() { + return lineStates.Length(); +} + +int CellBuffer::SetLevel(int line, int level) { + int prev = 0; + if ((line >= 0) && (line < lv.lines)) { + if (!lv.levels) { + lv.ExpandLevels(); + } + prev = lv.levels[line]; + if (lv.levels[line] != level) { + lv.levels[line] = level; + } + } + return prev; +} + +int CellBuffer::GetLevel(int line) { + if (lv.levels && (line >= 0) && (line < lv.lines)) { + return lv.levels[line]; + } else { + return SC_FOLDLEVELBASE; + } +} + diff --git a/src/CellBuffer.h b/src/CellBuffer.h new file mode 100644 index 000000000..5fbe2ea8a --- /dev/null +++ b/src/CellBuffer.h @@ -0,0 +1,197 @@ +// Scintilla source code edit control +// CellBuffer.h - manages the text of the document +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef CELLBUFFER_H +#define CELLBUFFER_H + +// This holds the marker identifier and the marker type to display. +// MarkerHandleNumbers are members of lists. +struct MarkerHandleNumber { + int handle; + int number; + MarkerHandleNumber *next; +}; + +// A marker handle set contains any number of MarkerHandleNumbers +class MarkerHandleSet { + MarkerHandleNumber *root; +public: + MarkerHandleSet(); + ~MarkerHandleSet(); + int Length(); + int NumberFromHandle(int handle); + int MarkValue(); // Bit set of marker numbers + bool Contains(int handle); + bool InsertHandle(int handle, int markerNum); + void RemoveHandle(int handle); + void RemoveNumber(int markerNum); + void CombineWith(MarkerHandleSet *other); +}; + +// Each line stores the starting position of the first character of the line in the cell buffer +// and potentially a marker handle set. Often a line will not have any attached markers. +struct LineData { + int startPosition; + MarkerHandleSet *handleSet; + LineData() : startPosition(0), handleSet(0) { + } +}; + +// The line vector contains information about each of the lines in a cell buffer. +class LineVector { +public: + enum { growSize = 4000 }; + int lines; + LineData *linesData; + int size; + int *levels; + int sizeLevels; + + // Handles are allocated sequentially and should never have to be reused as 32 bit ints are very big. + int handleCurrent; + + LineVector(); + ~LineVector(); + void Init(); + + void Expand(int sizeNew); + void ExpandLevels(int sizeNew=-1); + void InsertValue(int pos, int value); + void SetValue(int pos, int value); + void Remove(int pos); + int LineFromPosition(int pos); + + int AddMark(int line, int marker); + void MergeMarkers(int pos); + void DeleteMark(int line, int markerNum); + void DeleteMarkFromHandle(int markerHandle); + int LineFromHandle(int markerHandle); +}; + +// Actions are used to store all the information required to perform one undo/redo step. +enum actionType { insertAction, removeAction, startAction }; + +class Action { +public: + actionType at; + int position; + char *data; + int lenData; + + Action(); + ~Action(); + void Create(actionType at_, int position_=0, char *data_=0, int lenData_=0); + void Destroy(); + void Grab(Action *source); +}; + +enum undoCollectionType { undoCollectNone, undoCollectAutoStart, undoCollectManualStart }; + +// Holder for an expandable array of characters that supports undo and line markers +// Based on article "Data Structures in a Bit-Mapped Text Editor" +// by Wilfred J. Hansen, Byte January 1987, page 183 +class CellBuffer { +private: + char *body; + int size; + int length; + int part1len; + int gaplen; + char *part2body; + bool readOnly; + + Action *actions; + int lenActions; + int maxAction; + int currentAction; + undoCollectionType collectingUndo; + int undoSequenceDepth; + int savePoint; + + LineVector lv; + + SVector<int, 4000> lineStates; + + void GapTo(int position); + void RoomFor(int insertionLength); + + void EnsureUndoRoom(); + void AppendAction(actionType at, int position, char *data, int length); + + inline char ByteAt(int position); + void SetByteAt(int position, char ch); + +public: + + CellBuffer(int initialLength = 4000); + ~CellBuffer(); + + // Retrieving positions outside the range of the buffer works and returns 0 + char CharAt(int position); + void GetCharRange(char *buffer, int position, int lengthRetrieve); + char StyleAt(int position); + + int ByteLength(); + int Length(); + int Lines(); + int LineStart(int line); + int LineFromPosition(int pos) { return lv.LineFromPosition(pos); } + const char *InsertString(int position, char *s, int insertLength); + void InsertCharStyle(int position, char ch, char style); + + // Setting styles for positions outside the range of the buffer is safe and has no effect. + // True is returned if the style of a character changed. + bool SetStyleAt(int position, char style, char mask=(char)0xff); + bool SetStyleFor(int position, int length, char style, char mask); + + const char *DeleteChars(int position, int deleteLength); + + bool IsReadOnly(); + void SetReadOnly(bool set); + + // The save point is a marker in the undo stack where the container has stated that + // the buffer was saved. Undo and redo can move over the save point. + void SetSavePoint(); + bool IsSavePoint(); + + // Line marker functions + int AddMark(int line, int markerNum); + void DeleteMark(int line, int markerNum); + void DeleteMarkFromHandle(int markerHandle); + int GetMark(int line); + void DeleteAllMarks(int markerNum); + int LineFromHandle(int markerHandle); + + // Without undo + void BasicInsertString(int position, char *s, int insertLength); + void BasicDeleteChars(int position, int deleteLength); + + undoCollectionType SetUndoCollection(undoCollectionType collectUndo); + bool IsCollectingUndo(); + void AppendUndoStartAction(); + void BeginUndoAction(); + void EndUndoAction(); + void DeleteUndoHistory(); + + // To perform an undo, StartUndo is called to retreive the number of steps, then UndoStep is + // called that many times. Similarly for redo. + bool CanUndo(); + int StartUndo(); + const Action &UndoStep(); + bool CanRedo(); + int StartRedo(); + const Action &RedoStep(); + + int SetLineState(int line, int state); + int GetLineState(int line); + int GetMaxLineState(); + + int SetLevel(int line, int level); + int GetLevel(int line); +}; + +#define CELL_SIZE 2 + +#endif diff --git a/src/ContractionState.cxx b/src/ContractionState.cxx new file mode 100644 index 000000000..6f41461eb --- /dev/null +++ b/src/ContractionState.cxx @@ -0,0 +1,203 @@ +// Scintilla source code edit control +// ContractionState.cxx - manages visibility of lines for folding +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include "Platform.h" + +#include "ContractionState.h" + +OneLine::OneLine() { + displayLine = 0; + docLine = 0; + visible = true; + expanded = true; +} + +ContractionState::ContractionState() { + lines = 0; + size = 0; + linesInDoc = 1; + linesInDisplay = 1; + valid = false; +} + +ContractionState::~ContractionState() { + Clear(); +} + +void ContractionState::MakeValid() const { + if (!valid) { + // Could be cleverer by keeping the index of the last still valid entry + // rather than invalidating all. + int linePrev = -1; + int lineDisplay = 0; + for (int line=0; line<linesInDoc; line++) { + lines[line].displayLine = lineDisplay; + if (lines[line].visible) { + lines[lineDisplay].docLine = line; + lineDisplay++; + } + } + valid = true; + } +} + +void ContractionState::Clear() { + delete []lines; + lines = 0; + size = 0; + linesInDoc = 1; + linesInDisplay = 1; +} + +int ContractionState::LinesInDoc() const { + return linesInDoc; +} + +int ContractionState::LinesDisplayed() const { + return linesInDisplay; +} + +int ContractionState::DisplayFromDoc(int lineDoc) const { + if (size == 0) { + return lineDoc; + } + MakeValid(); + if ((lineDoc >= 0) && (lineDoc < linesInDoc)) { + return lines[lineDoc].displayLine; + } + return -1; +} + +int ContractionState::DocFromDisplay(int lineDisplay) const { + if (lineDisplay <= 0) + return 0; + if (lineDisplay >= linesInDisplay) + return linesInDoc-1; + if (size == 0) + return lineDisplay; + MakeValid(); + return lines[lineDisplay].docLine; +} + +void ContractionState::Grow(int sizeNew) { + OneLine *linesNew = new OneLine[sizeNew]; + if (linesNew) { + int i = 0; + for (; i < size; i++) { + linesNew[i] = lines[i]; + } + for (; i < sizeNew; i++) { + linesNew[i].displayLine = i; + } + delete []lines; + lines = linesNew; + size = sizeNew; + valid = false; + } else { + Platform::DebugPrintf("No memory available\n"); + // TODO: Blow up + } +} + +void ContractionState::InsertLines(int lineDoc, int lineCount) { + if (size == 0) { + linesInDoc += lineCount; + linesInDisplay += lineCount; + return; + } + //Platform::DebugPrintf("InsertLine[%d] = %d\n", lineDoc); + if ((linesInDoc + 2) >= size) { + Grow(size + growSize); + } + linesInDoc += lineCount; + linesInDisplay += lineCount; + for (int i = linesInDoc + 1; i >= lineDoc + lineCount; i--) { + lines[i].visible = lines[i - lineCount].visible; + lines[i].expanded = lines[i - lineCount].expanded; + } + for (int d=0;d<lineCount;d++) { + lines[lineDoc+d].visible = true; // Should inherit visibility from context ? + lines[lineDoc+d].expanded = true; + } + valid = false; +} + +void ContractionState::DeleteLines(int lineDoc, int lineCount) { + if (size == 0) { + linesInDoc -= lineCount; + linesInDisplay -= lineCount; + return; + } + int delta = 0; + for (int d=0;d<lineCount;d++) + if (lines[lineDoc+d].visible) + delta--; + for (int i = lineDoc; i < linesInDoc-lineCount; i++) { + lines[i].visible = lines[i + lineCount].visible; + lines[i].expanded = lines[i + lineCount].expanded; + } + linesInDoc -= lineCount; + linesInDisplay += delta; + valid = false; +} + +bool ContractionState::GetVisible(int lineDoc) const { + if (size == 0) + return true; + if ((lineDoc >= 0) && (lineDoc < linesInDoc)) { + return lines[lineDoc].visible; + } else { + return false; + } +} + +bool ContractionState::SetVisible(int lineDocStart, int lineDocEnd, bool visible) { + if (size == 0) { + Grow(lineDocEnd + growSize); + } + // TODO: modify docLine members to mirror displayLine + int delta = 0; + // Change lineDocs + if ((lineDocStart <= lineDocEnd) && (lineDocStart >= 0) && (lineDocEnd < linesInDoc)) { + for (int line=lineDocStart; line <= lineDocEnd; line++) { + if (lines[line].visible != visible) { + delta += visible ? 1 : -1; + lines[line].visible = visible; + } + lines[line].displayLine += delta; + } + if (delta != 0) { + for (int line=lineDocEnd+1; line <= linesInDoc; line++) { + lines[line].displayLine += delta; + } + } + } + linesInDisplay += delta; + valid = false; + return delta != 0; +} + +bool ContractionState::GetExpanded(int lineDoc) const { + if (size == 0) + return true; + if ((lineDoc >= 0) && (lineDoc < linesInDoc)) { + return lines[lineDoc].expanded; + } else { + return false; + } +} + +bool ContractionState::SetExpanded(int lineDoc, bool expanded) { + if (size == 0) { + Grow(lineDoc + growSize); + } + if ((lineDoc >= 0) && (lineDoc < linesInDoc)) { + if (lines[lineDoc].expanded != expanded) { + lines[lineDoc].expanded = expanded; + return true; + } + } + return false; +} diff --git a/src/ContractionState.h b/src/ContractionState.h new file mode 100644 index 000000000..9e17a7693 --- /dev/null +++ b/src/ContractionState.h @@ -0,0 +1,50 @@ +// Scintilla source code edit control +// ContractionState.h - manages visibility of lines for folding +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef CONTRACTIONSTATE_H +#define CONTRACTIONSTATE_H + +class OneLine { +public: + int displayLine; // position within set of visible lines + int docLine; // inverse of displayLine + bool visible; + bool expanded; + + OneLine(); + virtual ~OneLine() {} +}; + +class ContractionState { + void Grow(int sizeNew); + enum { growSize = 4000 }; + int linesInDoc; + int linesInDisplay; + mutable OneLine *lines; + int size; + mutable bool valid; + void MakeValid() const; +public: + ContractionState(); + virtual ~ContractionState(); + + void Clear(); + + int LinesInDoc() const; + int LinesDisplayed() const; + int DisplayFromDoc(int lineDoc) const; + int DocFromDisplay(int lineDisplay) const; + + void InsertLines(int lineDoc, int lineCount); + void DeleteLines(int lineDoc, int lineCount); + + bool GetVisible(int lineDoc) const; + bool SetVisible(int lineDocStart, int lineDocEnd, bool visible); + + bool GetExpanded(int lineDoc) const; + bool SetExpanded(int lineDoc, bool expanded); +}; + +#endif diff --git a/src/Document.cxx b/src/Document.cxx new file mode 100644 index 000000000..7d832241f --- /dev/null +++ b/src/Document.cxx @@ -0,0 +1,734 @@ +// Scintilla source code edit control +// Document.cxx - text document that handles notifications, DBCS, styling, words and end of line +// Copyright 1998-2000 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" + +#include "Scintilla.h" +#include "SVector.h" +#include "CellBuffer.h" +#include "Document.h" + +Document::Document() { + refCount = 0; +#ifdef unix + eolMode = SC_EOL_LF; +#else + eolMode = SC_EOL_CRLF; +#endif + dbcsCodePage = 0; + stylingBits = 5; + stylingBitsMask = 0x1F; + stylingPos = 0; + stylingMask = 0; + for (int ch = 0; ch < 256; ch++) { + wordchars[ch] = isalnum(ch) || ch == '_'; + } + endStyled = 0; + enteredCount = 0; + tabInChars = 8; + watchers = 0; + lenWatchers = 0; +} + +Document::~Document() { + for (int i = 0; i < lenWatchers; i++) { + watchers[i].watcher->NotifyDeleted(this, watchers[i].userData); + } + delete []watchers; + watchers = 0; + lenWatchers = 0; +} + +// Increase reference count and return its previous value. +int Document::AddRef() { + return refCount++; +} + +// Decrease reference count and return its provius value. +// Delete the document if reference count reaches zero. +int Document::Release() { + int curRefCount = --refCount; + if (curRefCount == 0) + delete this; + return curRefCount; +} + +void Document::SetSavePoint() { + cb.SetSavePoint(); + NotifySavePoint(true); +} + +int Document::LineStart(int line) { + return cb.LineStart(line); +} + +int Document::LineFromPosition(int pos) { + return cb.LineFromPosition(pos); +} + +int Document::LineEndPosition(int position) { + int line = LineFromPosition(position); + if (line == LinesTotal() - 1) { + position = LineStart(line + 1); + } else { + position = LineStart(line + 1) - 1; + // When line terminator is CR+LF, may need to go back one more + if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) { + position--; + } + } + return position; +} + +int Document::VCHomePosition(int position) { + int line = LineFromPosition(position); + int startPosition = LineStart(line); + int endLine = LineStart(line + 1) - 1; + int startText = startPosition; + while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) ) + startText++; + if (position == startText) + return startPosition; + else + return startText; +} + +int Document::SetLevel(int line, int level) { + int prev = cb.SetLevel(line, level); + if (prev != level) { + DocModification mh(SC_MOD_CHANGEFOLD, LineStart(line), 0, 0, 0); + mh.line = line; + mh.foldLevelNow = level; + mh.foldLevelPrev = prev; + NotifyModified(mh); + } + return prev; +} + +static bool IsSubordinate(int levelStart, int levelTry) { + if (levelTry & SC_FOLDLEVELWHITEFLAG) + return true; + else + return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK); +} + +int Document::GetLastChild(int lineParent, int level) { + if (level == -1) + level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK; + int maxLine = LinesTotal(); + int lineMaxSubord = lineParent; + while ((lineMaxSubord < maxLine-1) && IsSubordinate(level, GetLevel(lineMaxSubord+1))) { + lineMaxSubord++; + } + if (lineMaxSubord > lineParent) { + if (level > (GetLevel(lineMaxSubord+1) & SC_FOLDLEVELNUMBERMASK)) { + // Have chewed up some whitespace that belongs to a parent so seek back + if ((lineMaxSubord > lineParent) && (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG)) { + lineMaxSubord--; + } + } + } + return lineMaxSubord; +} + +int Document::GetFoldParent(int line) { + int level = GetLevel(line); + int lineLook = line-1; + while ((lineLook > 0) && ( + (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) || + ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level)) + ) { + lineLook--; + } + if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) && + ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) { + return lineLook; + } else { + return -1; + } +} + +int Document::ClampPositionIntoDocument(int pos) { + return Platform::Clamp(pos, 0, Length()); +} + +bool Document::IsCrLf(int pos) { + if (pos < 0) + return false; + if (pos >= (Length() - 1)) + return false; + return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n'); +} + +bool Document::IsDBCS(int pos) { +#if PLAT_WIN + if (dbcsCodePage) { + // Anchor DBCS calculations at start of line because start of line can + // not be a DBCS trail byte. + int startLine = pos; + while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n') + startLine--; + while (startLine <= pos) { + if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) { + startLine++; + if (startLine >= pos) + return true; + } + startLine++; + } + } + return false; +#else + return false; +#endif +} + +// Normalise a position so that it is not halfway through a two byte character. +// This can occur in two situations - +// When lines are terminated with \r\n pairs which should be treated as one character. +// When displaying DBCS text such as Japanese. +// If moving, move the position in the indicated direction. +int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) { + //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir); + // If out of range, just return value - should be fixed up after + if (pos < 0) + return pos; + if (pos > Length()) + return pos; + + // Position 0 and Length() can not be between any two characters + if (pos == 0) + return pos; + if (pos == Length()) + return pos; + + // assert pos > 0 && pos < Length() + if (checkLineEnd && IsCrLf(pos - 1)) { + if (moveDir > 0) + return pos + 1; + else + return pos - 1; + } + + // Not between CR and LF + +#if PLAT_WIN + if (dbcsCodePage) { + // Anchor DBCS calculations at start of line because start of line can + // not be a DBCS trail byte. + int startLine = pos; + while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n') + startLine--; + bool atLeadByte = false; + while (startLine < pos) { + if (atLeadByte) + atLeadByte = false; + else if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) + atLeadByte = true; + else + atLeadByte = false; + startLine++; + //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-"); + } + + if (atLeadByte) { + // Position is between a lead byte and a trail byte + if (moveDir > 0) + return pos + 1; + else + return pos - 1; + } + } +#endif + + return pos; +} + +void Document::ModifiedAt(int pos) { + if (endStyled > pos) + endStyled = pos; +} + +// Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt. +// SetStyleAt does not change the persistent state of a document + +// Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number +void Document::DeleteChars(int pos, int len) { + if (enteredCount == 0) { + enteredCount++; + if (cb.IsReadOnly()) + NotifyModifyAttempt(); + if (!cb.IsReadOnly()) { + int prevLinesTotal = LinesTotal(); + bool startSavePoint = cb.IsSavePoint(); + const char *text = cb.DeleteChars(pos*2, len * 2); + if (startSavePoint && cb.IsCollectingUndo()) + NotifySavePoint(!startSavePoint); + ModifiedAt(pos); + int modFlags = SC_MOD_DELETETEXT | SC_PERFORMED_USER; + DocModification mh(modFlags, pos, len, LinesTotal() - prevLinesTotal, text); + NotifyModified(mh); + } + enteredCount--; + } +} + +void Document::InsertStyledString(int position, char *s, int insertLength) { + if (enteredCount == 0) { + enteredCount++; + if (cb.IsReadOnly()) + NotifyModifyAttempt(); + if (!cb.IsReadOnly()) { + int prevLinesTotal = LinesTotal(); + bool startSavePoint = cb.IsSavePoint(); + const char *text = cb.InsertString(position, s, insertLength); + if (startSavePoint && cb.IsCollectingUndo()) + NotifySavePoint(!startSavePoint); + ModifiedAt(position / 2); + + int modFlags = SC_MOD_INSERTTEXT | SC_PERFORMED_USER; + DocModification mh(modFlags, position / 2, insertLength / 2, LinesTotal() - prevLinesTotal, text); + NotifyModified(mh); + } + enteredCount--; + } +} + +int Document::Undo() { + int newPos = 0; + if (enteredCount == 0) { + enteredCount++; + bool startSavePoint = cb.IsSavePoint(); + int steps = cb.StartUndo(); + for (int step=0; step<steps; step++) { + int prevLinesTotal = LinesTotal(); + const Action &action = cb.UndoStep(); + int cellPosition = action.position / 2; + ModifiedAt(cellPosition); + newPos = cellPosition; + + int modFlags = SC_PERFORMED_UNDO; + // With undo, an insertion action becomes a deletion notification + if (action.at == removeAction) { + newPos += action.lenData; + modFlags |= SC_MOD_INSERTTEXT; + } else { + modFlags |= SC_MOD_DELETETEXT; + } + if (step == steps-1) + modFlags |= SC_LASTSTEPINUNDOREDO; + NotifyModified(DocModification(modFlags, cellPosition, action.lenData, + LinesTotal() - prevLinesTotal, action.data)); + } + + bool endSavePoint = cb.IsSavePoint(); + if (startSavePoint != endSavePoint) + NotifySavePoint(endSavePoint); + enteredCount--; + } + return newPos; +} + +int Document::Redo() { + int newPos = 0; + if (enteredCount == 0) { + enteredCount++; + bool startSavePoint = cb.IsSavePoint(); + int steps = cb.StartRedo(); + for (int step=0; step<steps; step++) { + int prevLinesTotal = LinesTotal(); + const Action &action = cb.RedoStep(); + int cellPosition = action.position / 2; + ModifiedAt(cellPosition); + newPos = cellPosition; + + int modFlags = SC_PERFORMED_REDO; + if (action.at == insertAction) { + newPos += action.lenData; + modFlags |= SC_MOD_INSERTTEXT; + } else { + modFlags |= SC_MOD_DELETETEXT; + } + if (step == steps-1) + modFlags |= SC_LASTSTEPINUNDOREDO; + NotifyModified(DocModification(modFlags, cellPosition, action.lenData, + LinesTotal() - prevLinesTotal, action.data)); + } + + bool endSavePoint = cb.IsSavePoint(); + if (startSavePoint != endSavePoint) + NotifySavePoint(endSavePoint); + enteredCount--; + } + return newPos; +} + +void Document::InsertChar(int pos, char ch) { + char chs[2]; + chs[0] = ch; + chs[1] = 0; + InsertStyledString(pos*2, chs, 2); +} + +// Insert a null terminated string +void Document::InsertString(int position, const char *s) { + InsertString(position, s, strlen(s)); +} + +// Insert a string with a length +void Document::InsertString(int position, const char *s, int insertLength) { + char *sWithStyle = new char[insertLength * 2]; + if (sWithStyle) { + for (int i = 0; i < insertLength; i++) { + sWithStyle[i*2] = s[i]; + sWithStyle[i*2 + 1] = 0; + } + InsertStyledString(position*2, sWithStyle, insertLength*2); + delete []sWithStyle; + } +} + +void Document::DelChar(int pos) { + if (IsCrLf(pos)) { + DeleteChars(pos, 2); + } else if (IsDBCS(pos)) { + DeleteChars(pos, 2); + } else if (pos < Length()) { + DeleteChars(pos, 1); + } +} + +int Document::DelCharBack(int pos) { + if (pos <= 0) { + return pos; + } else if (IsCrLf(pos - 2)) { + DeleteChars(pos - 2, 2); + return pos - 2; + } else if (IsDBCS(pos - 1)) { + DeleteChars(pos - 2, 2); + return pos - 2; + } else { + DeleteChars(pos - 1, 1); + return pos - 1; + } +} + +void Document::Indent(bool forwards, int lineBottom, int lineTop) { + if (forwards) { + // Indent by a tab + for (int line = lineBottom; line >= lineTop; line--) { + InsertChar(LineStart(line), '\t'); + } + } else { + // Dedent - suck white space off the front of the line to dedent by equivalent of a tab + for (int line = lineBottom; line >= lineTop; line--) { + int ispc = 0; + while (ispc < tabInChars && cb.CharAt(LineStart(line) + ispc) == ' ') + ispc++; + int posStartLine = LineStart(line); + if (ispc == tabInChars) { + DeleteChars(posStartLine, ispc); + } else if (cb.CharAt(posStartLine + ispc) == '\t') { + DeleteChars(posStartLine, ispc + 1); + } else { // Hit a non-white + DeleteChars(posStartLine, ispc); + } + } + } +} + +void Document::ConvertLineEnds(int eolModeSet) { + BeginUndoAction(); + for (int pos = 0; pos < Length(); pos++) { + if (cb.CharAt(pos) == '\r') { + if (cb.CharAt(pos+1) == '\n') { + if (eolModeSet != SC_EOL_CRLF) { + DeleteChars(pos, 2); + if (eolModeSet == SC_EOL_CR) + InsertString(pos, "\r", 1); + else + InsertString(pos, "\n", 1); + } else { + pos++; + } + } else { + if (eolModeSet != SC_EOL_CR) { + DeleteChars(pos, 1); + if (eolModeSet == SC_EOL_CRLF) { + InsertString(pos, "\r\n", 2); + pos++; + } else { + InsertString(pos, "\n", 1); + } + } + } + } else if (cb.CharAt(pos) == '\n') { + if (eolModeSet != SC_EOL_LF) { + DeleteChars(pos, 1); + if (eolModeSet == SC_EOL_CRLF) { + InsertString(pos, "\r\n", 2); + pos++; + } else { + InsertString(pos, "\r", 1); + } + } + } + } + EndUndoAction(); +} + +bool Document::IsWordChar(unsigned char ch) { + return wordchars[ch]; +} + +int Document::ExtendWordSelect(int pos, int delta) { + if (delta < 0) { + while (pos > 0 && IsWordChar(cb.CharAt(pos - 1))) + pos--; + } else { + while (pos < (Length()) && IsWordChar(cb.CharAt(pos))) + pos++; + } + return pos; +} + +int Document::NextWordStart(int pos, int delta) { + if (delta < 0) { + while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t')) + pos--; + if (isspace(cb.CharAt(pos - 1))) { // Back up to previous line + while (pos > 0 && isspace(cb.CharAt(pos - 1))) + pos--; + } else { + bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1)); + while (pos > 0 && !isspace(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1)))) + pos--; + } + } else { + bool startAtWordChar = IsWordChar(cb.CharAt(pos)); + while (pos < (Length()) && isspace(cb.CharAt(pos))) + pos++; + while (pos < (Length()) && !isspace(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos)))) + pos++; + while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t')) + pos++; + } + return pos; +} + +bool Document::IsWordAt(int start, int end) { + int lengthDoc = Length(); + if (start > 0) { + char ch = CharAt(start - 1); + if (IsWordChar(ch)) + return false; + } + if (end < lengthDoc - 1) { + char ch = CharAt(end); + if (IsWordChar(ch)) + return false; + } + return true; +} + +// Find text in document, supporting both forward and backward +// searches (just pass minPos > maxPos to do a backward search) +// Has not been tested with backwards DBCS searches yet. +long Document::FindText(int minPos, int maxPos, const char *s, bool caseSensitive, bool word) { + bool forward = minPos <= maxPos; + int increment = forward ? 1 : -1; + + // Range endpoints should not be inside DBCS characters, but just in case, move them. + int startPos = MovePositionOutsideChar(minPos, increment, false); + int endPos = MovePositionOutsideChar(maxPos, increment, false); + + // Compute actual search ranges needed + int lengthFind = strlen(s); + int endSearch = 0; + if (startPos <= endPos) { + endSearch = endPos - lengthFind + 1; + } else { + endSearch = endPos; + } + //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind); + char firstChar = s[0]; + if (!caseSensitive) + firstChar = toupper(firstChar); + int pos = startPos; + while (forward ? (pos < endSearch) : (pos >= endSearch)) { + char ch = CharAt(pos); + if (caseSensitive) { + if (ch == firstChar) { + bool found = true; + for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) { + ch = CharAt(pos + posMatch); + if (ch != s[posMatch]) + found = false; + } + if (found) { + if ((!word) || IsWordAt(pos, pos + lengthFind)) + return pos; + } + } + } else { + if (toupper(ch) == firstChar) { + bool found = true; + for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) { + ch = CharAt(pos + posMatch); + if (toupper(ch) != toupper(s[posMatch])) + found = false; + } + if (found) { + if ((!word) || IsWordAt(pos, pos + lengthFind)) + return pos; + } + } + } + pos += increment; + if (dbcsCodePage) { + // Ensure trying to match from start of character + pos = MovePositionOutsideChar(pos, increment, false); + } + } + //Platform::DebugPrintf("Not found\n"); + return - 1; +} + +int Document::LinesTotal() { + return cb.Lines(); +} + +void Document::SetWordChars(unsigned char *chars) { + int ch; + for (ch = 0; ch < 256; ch++) { + wordchars[ch] = false; + } + if (chars) { + while (*chars) { + wordchars[*chars] = true; + chars++; + } + } else { + for (ch = 0; ch < 256; ch++) { + wordchars[ch] = isalnum(ch) || ch == '_'; + } + } +} + +void Document::SetStylingBits(int bits) { + stylingBits = bits; + stylingBitsMask = 0; + for (int bit=0; bit<stylingBits; bit++) { + stylingBitsMask <<= 1; + stylingBitsMask |= 1; + } +} + +void Document::StartStyling(int position, char mask) { + stylingPos = position; + stylingMask = mask; +} + +void Document::SetStyleFor(int length, char style) { + if (enteredCount == 0) { + enteredCount++; + int prevEndStyled = endStyled; + if (cb.SetStyleFor(stylingPos, length, style, stylingMask)) { + DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER, + prevEndStyled, length); + NotifyModified(mh); + } + stylingPos += length; + endStyled = stylingPos; + enteredCount--; + } +} + +void Document::SetStyles(int length, char *styles) { + if (enteredCount == 0) { + enteredCount++; + int prevEndStyled = endStyled; + bool didChange = false; + for (int iPos = 0; iPos < length; iPos++, stylingPos++) { + if (cb.SetStyleAt(stylingPos, styles[iPos], stylingMask)) { + didChange = true; + } + } + endStyled = stylingPos; + if (didChange) { + DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER, + prevEndStyled, endStyled - prevEndStyled); + NotifyModified(mh); + } + enteredCount--; + } +} + +bool Document::AddWatcher(DocWatcher *watcher, void *userData) { + for (int i = 0; i < lenWatchers; i++) { + if ((watchers[i].watcher == watcher) && + (watchers[i].userData == userData)) + return false; + } + WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1]; + if (!pwNew) + return false; + for (int j = 0; j < lenWatchers; j++) + pwNew[j] = watchers[j]; + pwNew[lenWatchers].watcher = watcher; + pwNew[lenWatchers].userData = userData; + delete []watchers; + watchers = pwNew; + lenWatchers++; + return true; +} + +bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) { + for (int i = 0; i < lenWatchers; i++) { + if ((watchers[i].watcher == watcher) && + (watchers[i].userData == userData)) { + if (lenWatchers == 1) { + delete []watchers; + watchers = 0; + lenWatchers = 0; + } else { + WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers]; + if (!pwNew) + return false; + for (int j = 0; j < lenWatchers - 1; j++) { + pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1]; + } + delete []watchers; + watchers = pwNew; + lenWatchers--; + } + return true; + } + } + return false; +} + +void Document::NotifyModifyAttempt() { + for (int i = 0; i < lenWatchers; i++) { + watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData); + } +} + +void Document::NotifySavePoint(bool atSavePoint) { + for (int i = 0; i < lenWatchers; i++) { + watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint); + } +} + +void Document::NotifyModified(DocModification mh) { + for (int i = 0; i < lenWatchers; i++) { + watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData); + } +} diff --git a/src/Document.h b/src/Document.h new file mode 100644 index 000000000..fba611c7f --- /dev/null +++ b/src/Document.h @@ -0,0 +1,222 @@ +// Scintilla source code edit control +// Document.h - text document that handles notifications, DBCS, styling, words and end of line +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef DOCUMENT_H +#define DOCUMENT_H + +// A Position is a position within a document between two characters or at the beginning or end. +// Sometimes used as a character index where it identifies the character after the position. +typedef int Position; +const Position invalidPosition = -1; + +// The range class represents a range of text in a document. +// The two values are not sorted as one end may be more significant than the other +// as is the case for the selection where the end position is the position of the caret. +// If either position is invalidPosition then the range is invalid and most operations will fail. +class Range { +public: + Position start; + Position end; + + Range(Position pos=0) : + start(pos), end(pos) { + }; + Range(Position start_, Position end_) : + start(start_), end(end_) { + }; + + bool Valid() const { + return (start != invalidPosition) && (end != invalidPosition); + } + + bool Contains(Position pos) const { + if (start < end) { + return (pos >= start && pos <= end); + } else { + return (pos <= start && pos >= end); + } + } + + bool Contains(Range other) const { + return Contains(other.start) && Contains(other.end); + } + + bool Overlaps(Range other) const { + return + Contains(other.start) || + Contains(other.end) || + other.Contains(start) || + other.Contains(end); + } +}; + +class DocWatcher; +class DocModification; + +class Document { + +public: + // Used to pair watcher pointer with user data + class WatcherWithUserData { + public: + DocWatcher *watcher; + void *userData; + WatcherWithUserData() { + watcher = 0; + userData = 0; + } + }; + +private: + int refCount; + CellBuffer cb; + bool wordchars[256]; + int stylingPos; + int stylingMask; + int endStyled; + int enteredCount; + + WatcherWithUserData *watchers; + int lenWatchers; + +public: + int stylingBits; + int stylingBitsMask; + + int eolMode; + int dbcsCodePage; + int tabInChars; + + Document(); + virtual ~Document(); + + int AddRef(); + int Release(); + + int LineFromPosition(int pos); + int ClampPositionIntoDocument(int pos); + bool IsCrLf(int pos); + int MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd=true); + + // Gateways to modifying document + void DeleteChars(int pos, int len); + void InsertStyledString(int position, char *s, int insertLength); + int Undo(); + int Redo(); + bool CanUndo() { return cb.CanUndo(); } + bool CanRedo() { return cb.CanRedo(); } + void DeleteUndoHistory() { cb.DeleteUndoHistory(); } + undoCollectionType SetUndoCollection(undoCollectionType collectUndo) { + return cb.SetUndoCollection(collectUndo); + } + void AppendUndoStartAction() { cb.AppendUndoStartAction(); } + void BeginUndoAction() { cb.BeginUndoAction(); } + void EndUndoAction() { cb.EndUndoAction(); } + void SetSavePoint(); + bool IsSavePoint() { return cb.IsSavePoint(); } + void Indent(bool forwards, int lineBottom, int lineTop); + void ConvertLineEnds(int eolModeSet); + void SetReadOnly(bool set) { cb.SetReadOnly(set); } + + void InsertChar(int pos, char ch); + void InsertString(int position, const char *s); + void InsertString(int position, const char *s, int insertLength); + void DelChar(int pos); + int DelCharBack(int pos); + + char CharAt(int position) { return cb.CharAt(position); } + void GetCharRange(char *buffer, int position, int lengthRetrieve) { + cb.GetCharRange(buffer, position, lengthRetrieve); + } + char StyleAt(int position) { return cb.StyleAt(position); } + int GetMark(int line) { return cb.GetMark(line); } + int AddMark(int line, int markerNum) { return cb.AddMark(line, markerNum); } + void DeleteMark(int line, int markerNum) { cb.DeleteMark(line, markerNum); } + void DeleteMarkFromHandle(int markerHandle) { cb.DeleteMarkFromHandle(markerHandle); } + void DeleteAllMarks(int markerNum) { cb.DeleteAllMarks(markerNum); } + int LineFromHandle(int markerHandle) { return cb.LineFromHandle(markerHandle); } + int LineStart(int line); + int LineEndPosition(int position); + int VCHomePosition(int position); + + int SetLevel(int line, int level); + int GetLevel(int line) { return cb.GetLevel(line); } + int GetLastChild(int lineParent, int level=-1); + int GetFoldParent(int line); + + void Indent(bool forwards); + int ExtendWordSelect(int pos, int delta); + int NextWordStart(int pos, int delta); + int Length() { return cb.Length(); } + long FindText(int minPos, int maxPos, const char *s, bool caseSensitive, bool word); + long FindText(WORD iMessage,WPARAM wParam,LPARAM lParam); + int LinesTotal(); + + void SetWordChars(unsigned char *chars); + void SetStylingBits(int bits); + void StartStyling(int position, char mask); + void SetStyleFor(int length, char style); + void SetStyles(int length, char *styles); + int GetEndStyled() { return endStyled; } + + int SetLineState(int line, int state) { return cb.SetLineState(line, state); } + int GetLineState(int line) { return cb.GetLineState(line); } + int GetMaxLineState() { return cb.GetMaxLineState(); } + + bool AddWatcher(DocWatcher *watcher, void *userData); + bool RemoveWatcher(DocWatcher *watcher, void *userData); + const WatcherWithUserData *GetWatchers() const { return watchers; } + int GetLenWatchers() const { return lenWatchers; } + +private: + bool IsDBCS(int pos); + bool IsWordChar(unsigned char ch); + bool IsWordAt(int start, int end); + void ModifiedAt(int pos); + + void NotifyModifyAttempt(); + void NotifySavePoint(bool atSavePoint); + void NotifyModified(DocModification mh); +}; + +// To optimise processing of document modifications by DocWatchers, a hint is passed indicating the +// scope of the change. +// If the DocWatcher is a document view then this can be used to optimise screen updating. +class DocModification { +public: + int modificationType; + int position; + int length; + int linesAdded; // Negative if lines deleted + const char *text; // Only valid for changes to text, not for changes to style + int line; + int foldLevelNow; + int foldLevelPrev; + + DocModification(int modificationType_, int position_=0, int length_=0, + int linesAdded_=0, const char *text_=0) : + modificationType(modificationType_), + position(position_), + length(length_), + linesAdded(linesAdded_), + text(text_), + line(0), + foldLevelNow(0), + foldLevelPrev(0) {} +}; + +// A class that wants to receive notifications from a Document must be derived from DocWatcher +// and implement the notification methods. It can then be added to the watcher list with AddWatcher. +class DocWatcher { +public: + virtual ~DocWatcher() {} + + virtual void NotifyModifyAttempt(Document *doc, void *userData) = 0; + virtual void NotifySavePoint(Document *doc, void *userData, bool atSavePoint) = 0; + virtual void NotifyModified(Document *doc, DocModification mh, void *userData) = 0; + virtual void NotifyDeleted(Document *doc, void *userData) = 0; +}; + +#endif |