]> git.saurik.com Git - wxWidgets.git/blobdiff - contrib/src/stc/scintilla/src/CellBuffer.cxx
Updated Watcom/OS2 makefiles.
[wxWidgets.git] / contrib / src / stc / scintilla / src / CellBuffer.cxx
index 777688511a657a67ed0414993565cb9482f899f0..1109a17fbe54293c865c78d86cb17f0a5ae29bca 100644 (file)
@@ -1,6 +1,8 @@
 // Scintilla source code edit control
-// CellBuffer.cxx - manages a buffer of cells
-// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org>
+/** @file CellBuffer.cxx
+ ** Manages a buffer of cells.
+ **/
+// Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
 // The License.txt file describes the conditions under which this software may be distributed.
 
 #include <stdio.h>
@@ -88,23 +90,26 @@ void MarkerHandleSet::RemoveHandle(int handle) {
                if (mhn->handle == handle) {
                        *pmhn = mhn->next;
                        delete mhn;
-                       return;
+                       return ;
                }
                pmhn = &((*pmhn)->next);
        }
 }
 
-void MarkerHandleSet::RemoveNumber(int markerNum) {
+bool MarkerHandleSet::RemoveNumber(int markerNum) {
+       bool performedDeletion = false;
        MarkerHandleNumber **pmhn = &root;
        while (*pmhn) {
                MarkerHandleNumber *mhn = *pmhn;
                if (mhn->number == markerNum) {
                        *pmhn = mhn->next;
                        delete mhn;
-                       return;
+                       performedDeletion = true;
+               } else {
+                       pmhn = &((*pmhn)->next);
                }
-               pmhn = &((*pmhn)->next);
        }
+       return performedDeletion;
 }
 
 void MarkerHandleSet::CombineWith(MarkerHandleSet *other) {
@@ -119,7 +124,12 @@ void MarkerHandleSet::CombineWith(MarkerHandleSet *other) {
 LineVector::LineVector() {
        linesData = 0;
        lines = 0;
+       size = 0;
        levels = 0;
+       sizeLevels = 0;
+       handleCurrent = 1;
+       growSize = 1000;
+
        Init();
 }
 
@@ -161,6 +171,7 @@ void LineVector::Expand(int sizeNew) {
                Platform::DebugPrintf("No memory available\n");
                // TODO: Blow up
        }
+
 }
 
 void LineVector::ExpandLevels(int sizeNew) {
@@ -180,22 +191,43 @@ void LineVector::ExpandLevels(int sizeNew) {
                Platform::DebugPrintf("No memory available\n");
                // TODO: Blow up
        }
+
+}
+
+void LineVector::ClearLevels() {
+       delete []levels;
+       levels = 0;
+       sizeLevels = 0;
 }
 
 void LineVector::InsertValue(int pos, int value) {
        //Platform::DebugPrintf("InsertValue[%d] = %d\n", pos, value);
        if ((lines + 2) >= size) {
+               if (growSize * 6 < size)
+                       growSize *= 2;
                Expand(size + growSize);
                if (levels) {
                        ExpandLevels(size + growSize);
                }
        }
        lines++;
-       for (int i = lines + 1; i > pos; i--) {
+       for (int i = lines; i > pos; i--) {
                linesData[i] = linesData[i - 1];
        }
        linesData[pos].startPosition = value;
        linesData[pos].handleSet = 0;
+       if (levels) {
+               for (int j = lines; j > pos; j--) {
+                       levels[j] = levels[j - 1];
+               }
+               if (pos == 0) {
+                       levels[pos] = SC_FOLDLEVELBASE;
+               } else if (pos == (lines - 1)) {        // Last line will not be a folder
+                       levels[pos] = SC_FOLDLEVELBASE;
+               } else {
+                       levels[pos] = levels[pos - 1];
+               }
+       }
 }
 
 void LineVector::SetValue(int pos, int value) {
@@ -221,6 +253,16 @@ void LineVector::Remove(int pos) {
        for (int i = pos; i < lines; i++) {
                linesData[i] = linesData[i + 1];
        }
+       if (levels) {
+               // Move up following lines but merge header flag from this line
+               // to line before to avoid a temporary disappearence causing expansion.
+               int firstHeader = levels[pos] & SC_FOLDLEVELHEADERFLAG;
+               for (int j = pos; j < lines; j++) {
+                       levels[j] = levels[j + 1];
+               }
+               if (pos > 0)
+                       levels[pos-1] |= firstHeader;
+       }
        lines--;
 }
 
@@ -233,9 +275,8 @@ int LineVector::LineFromPosition(int pos) {
                return lines - 1;
        int lower = 0;
        int upper = lines;
-       int middle = 0;
        do {
-               middle = (upper + lower + 1) / 2;       // Round high
+               int middle = (upper + lower + 1) / 2;   // Round high
                if (pos < linesData[middle].startPosition) {
                        upper = middle - 1;
                } else {
@@ -260,21 +301,27 @@ int LineVector::AddMark(int line, int markerNum) {
 }
 
 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;
-               }
+       if (linesData[pos + 1].handleSet != NULL) {
+               if (linesData[pos].handleSet == NULL )
+                       linesData[pos].handleSet = new MarkerHandleSet;
+               linesData[pos].handleSet->CombineWith(linesData[pos + 1].handleSet);
+               delete linesData[pos + 1].handleSet;
+               linesData[pos + 1].handleSet = NULL;
        }
 }
 
-void LineVector::DeleteMark(int line, int markerNum) {
+void LineVector::DeleteMark(int line, int markerNum, bool all) {
        if (linesData[line].handleSet) {
                if (markerNum == -1) {
                        delete linesData[line].handleSet;
                        linesData[line].handleSet = 0;
                } else {
-                       linesData[line].handleSet->RemoveNumber(markerNum);
+                       bool performedDeletion = 
+                               linesData[line].handleSet->RemoveNumber(markerNum);
+                       while (all && performedDeletion) {
+                               performedDeletion = 
+                                       linesData[line].handleSet->RemoveNumber(markerNum);
+                       }
                        if (linesData[line].handleSet->Length() == 0) {
                                delete linesData[line].handleSet;
                                linesData[line].handleSet = 0;
@@ -316,12 +363,13 @@ Action::~Action() {
        Destroy();
 }
 
-void Action::Create(actionType at_, int position_, char *data_, int lenData_) {
+void Action::Create(actionType at_, int position_, char *data_, int lenData_, bool mayCoalesce_) {
        delete []data;
        position = position_;
        at = at_;
        data = data_;
        lenData = lenData_;
+       mayCoalesce = mayCoalesce_;
 }
 
 void Action::Destroy() {
@@ -336,12 +384,220 @@ void Action::Grab(Action *source) {
        at = source->at;
        data = source->data;
        lenData = source->lenData;
+       mayCoalesce = source->mayCoalesce;
 
        // Ownership of source data transferred to this
        source->position = 0;
        source->at = startAction;
        source->data = 0;
        source->lenData = 0;
+       source->mayCoalesce = true;
+}
+
+// The undo history stores a sequence of user operations that represent the user's view of the
+// commands executed on the text.
+// Each user operation contains a sequence of text insertion and text deletion actions.
+// All the user operations are stored in a list of individual actions with 'start' actions used
+// as delimiters between user operations.
+// Initially there is one start action in the history.
+// As each action is performed, it is recorded in the history. The action may either become
+// part of the current user operation or may start a new user operation. If it is to be part of the
+// current operation, then it overwrites the current last action. If it is to be part of a new
+// operation, it is appended after the current last action.
+// After writing the new action, a new start action is appended at the end of the history.
+// The decision of whether to start a new user operation is based upon two factors. If a
+// compound operation has been explicitly started by calling BeginUndoAction and no matching
+// EndUndoAction (these calls nest) has been called, then the action is coalesced into the current
+// operation. If there is no outstanding BeginUndoAction call then a new operation is started
+// unless it looks as if the new action is caused by the user typing or deleting a stream of text.
+// Sequences that look like typing or deletion are coalesced into a single user operation.
+
+UndoHistory::UndoHistory() {
+
+       lenActions = 100;
+       actions = new Action[lenActions];
+       maxAction = 0;
+       currentAction = 0;
+       undoSequenceDepth = 0;
+       savePoint = 0;
+
+       actions[currentAction].Create(startAction);
+}
+
+UndoHistory::~UndoHistory() {
+       delete []actions;
+       actions = 0;
+}
+
+void UndoHistory::EnsureUndoRoom() {
+       // Have to test that there is room for 2 more actions in the array
+       // as two actions may be created by the calling 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 UndoHistory::AppendAction(actionType at, int position, char *data, int lengthData) {
+       EnsureUndoRoom();
+       //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction);
+       //Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at,
+       //      actions[currentAction - 1].position, actions[currentAction - 1].lenData);
+       if (currentAction < savePoint) {
+               savePoint = -1;
+       }
+       if (currentAction >= 1) {
+               if (0 == undoSequenceDepth) {
+                       // Top level actions may not always be coalesced
+                       Action &actPrevious = actions[currentAction - 1];
+                       // See if current action can be coalesced into previous action
+                       // Will work if both are inserts or deletes and position is same
+                       if (at != actPrevious.at) {
+                               currentAction++;
+                       } else if (currentAction == savePoint) {
+                               currentAction++;
+                       } else if ((at == insertAction) &&
+                                  (position != (actPrevious.position + actPrevious.lenData))) {
+                               // Insertions must be immediately after to coalesce
+                               currentAction++;
+                       } else if (!actions[currentAction].mayCoalesce) {
+                               // Not allowed to coalesce if this set
+                               currentAction++;
+                       } else if (at == removeAction) {
+                               if ((lengthData == 1) || (lengthData == 2)){
+                                       if ((position + lengthData) == actPrevious.position) {
+                                               ; // Backspace -> OK
+                                       } else if (position == actPrevious.position) {
+                                               ; // Delete -> OK
+                                       } else {
+                                               // Removals must be at same position to coalesce
+                                               currentAction++;
+                                       }
+                               } else {
+                                       // Removals must be of one character to coalesce
+                                       currentAction++;
+                               }
+                       } else {
+                               //Platform::DebugPrintf("action coalesced\n");
+                       }
+
+               } else {
+                       // Actions not at top level are always coalesced unless this is after return to top level
+                       if (!actions[currentAction].mayCoalesce)
+                               currentAction++;
+               }
+       } else {
+               currentAction++;
+       }
+       actions[currentAction].Create(at, position, data, lengthData);
+       currentAction++;
+       actions[currentAction].Create(startAction);
+       maxAction = currentAction;
+}
+
+void UndoHistory::BeginUndoAction() {
+       EnsureUndoRoom();
+       if (undoSequenceDepth == 0) {
+               if (actions[currentAction].at != startAction) {
+                       currentAction++;
+                       actions[currentAction].Create(startAction);
+                       maxAction = currentAction;
+               }
+               actions[currentAction].mayCoalesce = false;
+       }
+       undoSequenceDepth++;
+}
+
+void UndoHistory::EndUndoAction() {
+       EnsureUndoRoom();
+       undoSequenceDepth--;
+       if (0 == undoSequenceDepth) {
+               if (actions[currentAction].at != startAction) {
+                       currentAction++;
+                       actions[currentAction].Create(startAction);
+                       maxAction = currentAction;
+               }
+               actions[currentAction].mayCoalesce = false;
+       }
+}
+
+void UndoHistory::DropUndoSequence() {
+       undoSequenceDepth = 0;
+}
+
+void UndoHistory::DeleteUndoHistory() {
+       for (int i = 1; i < maxAction; i++)
+               actions[i].Destroy();
+       maxAction = 0;
+       currentAction = 0;
+       actions[currentAction].Create(startAction);
+       savePoint = 0;
+}
+
+void UndoHistory::SetSavePoint() {
+       savePoint = currentAction;
+}
+
+bool UndoHistory::IsSavePoint() const {
+       return savePoint == currentAction;
+}
+
+bool UndoHistory::CanUndo() const {
+       return (currentAction > 0) && (maxAction > 0);
+}
+
+int UndoHistory::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 &UndoHistory::GetUndoStep() const {
+       return actions[currentAction];
+}
+
+void UndoHistory::CompletedUndoStep() {
+       currentAction--;
+}
+
+bool UndoHistory::CanRedo() const {
+       return maxAction > currentAction;
+}
+
+int UndoHistory::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 &UndoHistory::GetRedoStep() const {
+       return actions[currentAction];
+}
+
+void UndoHistory::CompletedRedoStep() {
+       currentAction++;
 }
 
 CellBuffer::CellBuffer(int initialLength) {
@@ -352,28 +608,18 @@ CellBuffer::CellBuffer(int initialLength) {
        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);
+       collectingUndo = true;
+       growSize = 4000;
 }
 
 CellBuffer::~CellBuffer() {
        delete []body;
        body = 0;
-       delete []actions;
-       actions = 0;
 }
 
 void CellBuffer::GapTo(int position) {
        if (position == part1len)
-               return;
+               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);
@@ -393,17 +639,10 @@ 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);
+               if (growSize * 6 < size)
+                       growSize *= 2;
+               int newSize = size + insertionLength + growSize;
+               Allocate(newSize);
        }
 }
 
@@ -429,16 +668,16 @@ void CellBuffer::SetByteAt(int position, char ch) {
 
        if (position < 0) {
                //Platform::DebugPrintf("Bad position %d\n",position);
-               return;
+               return ;
        }
        if (position >= length + 11) {
                Platform::DebugPrintf("Very Bad position %d of %d\n", position, length);
                //exit(2);
-               return;
+               return ;
        }
        if (position >= length) {
                //Platform::DebugPrintf("Bad position %d of %d\n",position,length);
-               return;
+               return ;
        }
 
        if (position < part1len) {
@@ -454,20 +693,20 @@ char CellBuffer::CharAt(int position) {
 
 void CellBuffer::GetCharRange(char *buffer, int position, int lengthRetrieve) {
        if (lengthRetrieve < 0)
-               return;
+               return ;
        if (position < 0)
-               return;
+               return ;
        int bytePos = position * 2;
        if ((bytePos + lengthRetrieve * 2) > length) {
-               Platform::DebugPrintf("Bad GetCharRange %d for %d of %d\n",bytePos,
-                       lengthRetrieve, length);
-               return;
+               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;
+               pb += 2;
        }
 }
 
@@ -486,7 +725,7 @@ const char *CellBuffer::InsertString(int position, char *s, int insertLength) {
                        for (int i = 0; i < insertLength / 2; i++) {
                                data[i] = s[i * 2];
                        }
-                       AppendAction(insertAction, position, data, insertLength / 2);
+                       uh.AppendAction(insertAction, position / 2, data, insertLength / 2);
                }
 
                BasicInsertString(position, s, insertLength);
@@ -494,17 +733,11 @@ const char *CellBuffer::InsertString(int position, char *s, int 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);
+       style &= mask;
+       char curVal = ByteAt(position * 2 + 1);
        if ((curVal & mask) != style) {
-               SetByteAt(position*2 + 1, (curVal & ~mask) | style);
+               SetByteAt(position*2 + 1, static_cast<char>((curVal & ~mask) | style));
                return true;
        } else {
                return false;
@@ -514,10 +747,12 @@ bool CellBuffer::SetStyleAt(int position, char style, char mask) {
 bool CellBuffer::SetStyleFor(int position, int lengthStyle, char style, char mask) {
        int bytePos = position * 2 + 1;
        bool changed = false;
+       PLATFORM_ASSERT(lengthStyle == 0 ||
+               (lengthStyle > 0 && lengthStyle + position < length));
        while (lengthStyle--) {
                char curVal = ByteAt(bytePos);
                if ((curVal & mask) != style) {
-                       SetByteAt(bytePos, (curVal & ~mask) | style);
+                       SetByteAt(bytePos, static_cast<char>((curVal & ~mask) | style));
                        changed = true;
                }
                bytePos += 2;
@@ -525,50 +760,9 @@ bool CellBuffer::SetStyleFor(int position, int lengthStyle, char style, char mas
        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
+       PLATFORM_ASSERT(deleteLength > 0);
        char *data = 0;
        if (!readOnly) {
                if (collectingUndo) {
@@ -577,7 +771,7 @@ const char *CellBuffer::DeleteChars(int position, int deleteLength) {
                        for (int i = 0; i < deleteLength / 2; i++) {
                                data[i] = ByteAt(position + i * 2);
                        }
-                       AppendAction(removeAction, position, data, deleteLength / 2);
+                       uh.AppendAction(removeAction, position / 2, data, deleteLength / 2);
                }
 
                BasicDeleteChars(position, deleteLength);
@@ -593,6 +787,19 @@ int CellBuffer::Length() {
        return ByteLength() / 2;
 }
 
+void CellBuffer::Allocate(int newSize) {
+       if (newSize > length) {
+               GapTo(length);
+               char *newBody = new char[newSize];
+               memcpy(newBody, body, length);
+               delete []body;
+               body = newBody;
+               gaplen += newSize - size;
+               part2body = body + gaplen;
+               size = newSize;
+       }
+}
+
 int CellBuffer::Lines() {
        //Platform::DebugPrintf("Lines = %d\n", lv.lines);
        return lv.lines;
@@ -602,7 +809,7 @@ int CellBuffer::LineStart(int line) {
        if (line < 0)
                return 0;
        else if (line > lv.lines)
-               return length;
+               return Length();
        else
                return lv.linesData[line].startPosition;
 }
@@ -616,11 +823,11 @@ void CellBuffer::SetReadOnly(bool set) {
 }
 
 void CellBuffer::SetSavePoint() {
-       savePoint = currentAction;
+       uh.SetSavePoint();
 }
 
 bool CellBuffer::IsSavePoint() {
-       return savePoint == currentAction;
+       return uh.IsSavePoint();
 }
 
 int CellBuffer::AddMark(int line, int markerNum) {
@@ -632,7 +839,7 @@ int CellBuffer::AddMark(int line, int markerNum) {
 
 void CellBuffer::DeleteMark(int line, int markerNum) {
        if ((line >= 0) && (line < lv.lines)) {
-               lv.DeleteMark(line, markerNum);
+               lv.DeleteMark(line, markerNum, false);
        }
 }
 
@@ -648,7 +855,7 @@ int CellBuffer::GetMark(int line) {
 
 void CellBuffer::DeleteAllMarks(int markerNum) {
        for (int line = 0; line < lv.lines; line++) {
-               lv.DeleteMark(line, markerNum);
+               lv.DeleteMark(line, markerNum, true);
        }
 }
 
@@ -661,7 +868,8 @@ int CellBuffer::LineFromHandle(int markerHandle) {
 void CellBuffer::BasicInsertString(int position, char *s, int insertLength) {
        //Platform::DebugPrintf("Inserting at %d for %d\n", position, insertLength);
        if (insertLength == 0)
-               return;
+               return ;
+       PLATFORM_ASSERT(insertLength > 0);
        RoomFor(insertLength);
        GapTo(position);
 
@@ -721,7 +929,7 @@ void CellBuffer::BasicInsertString(int position, char *s, int insertLength) {
 void CellBuffer::BasicDeleteChars(int position, int deleteLength) {
        //Platform::DebugPrintf("Deleting at %d for %d\n", position, deleteLength);
        if (deleteLength == 0)
-               return;
+               return ;
 
        if ((position == 0) && (deleteLength == length)) {
                // If whole buffer is being deleted, faster to reinitialise lines data
@@ -764,10 +972,13 @@ void CellBuffer::BasicDeleteChars(int position, int deleteLength) {
                                        //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
+                       } else if (ch == '\n') {
+                               if (ignoreNL) {
+                                       ignoreNL = false;       // Further \n are real deletions
+                               } else {
+                                       //Platform::DebugPrintf("Removing lf end of line\n");
+                                       lv.Remove(lineRemove);
+                               }
                        }
 
                        ch = chNext;
@@ -790,9 +1001,9 @@ void CellBuffer::BasicDeleteChars(int position, int deleteLength) {
        part2body = body + gaplen;
 }
 
-undoCollectionType CellBuffer::SetUndoCollection(undoCollectionType collectUndo) {
+bool CellBuffer::SetUndoCollection(bool collectUndo) {
        collectingUndo = collectUndo;
-       undoSequenceDepth = 0;
+       uh.DropUndoSequence();
        return collectingUndo;
 }
 
@@ -800,116 +1011,72 @@ 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++;
+       uh.BeginUndoAction();
 }
 
 void CellBuffer::EndUndoAction() {
-       EnsureUndoRoom();
-       undoSequenceDepth--;
-       if (0 == undoSequenceDepth) {
-               if (actions[currentAction].at != startAction) {
-                       currentAction++;
-                       actions[currentAction].Create(startAction);
-                       maxAction = currentAction;
-               }
-       }
+       uh.EndUndoAction();
 }
 
 void CellBuffer::DeleteUndoHistory() {
-       for (int i = 1; i < maxAction; i++)
-               actions[i].Destroy();
-       maxAction = 0;
-       currentAction = 0;
-       savePoint = 0;
+       uh.DeleteUndoHistory();
 }
 
 bool CellBuffer::CanUndo() {
-       return (!readOnly) && ((currentAction > 0) && (maxAction > 0));
+       return uh.CanUndo();
 }
 
 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;
+       return uh.StartUndo();
+}
+
+const Action &CellBuffer::GetUndoStep() const {
+       return uh.GetUndoStep();
 }
 
-const Action &CellBuffer::UndoStep() {
-       const Action &actionStep = actions[currentAction];
+void CellBuffer::PerformUndoStep() {
+       const Action &actionStep = uh.GetUndoStep();
        if (actionStep.at == insertAction) {
-               BasicDeleteChars(actionStep.position, actionStep.lenData*2);
+               BasicDeleteChars(actionStep.position*2, 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;
+                       styledData[i*2 + 1] = 0;
                }
-               BasicInsertString(actionStep.position, styledData, actionStep.lenData*2);
+               BasicInsertString(actionStep.position*2, styledData, actionStep.lenData*2);
                delete []styledData;
        }
-       currentAction--;
-       return actionStep;
+       uh.CompletedUndoStep();
 }
 
 bool CellBuffer::CanRedo() {
-       return (!readOnly) && (maxAction > currentAction);
+       return uh.CanRedo();
 }
 
 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;
+       return uh.StartRedo();
+}
+
+const Action &CellBuffer::GetRedoStep() const {
+       return uh.GetRedoStep();
 }
 
-const Action &CellBuffer::RedoStep() {
-       const Action &actionStep = actions[currentAction];
+void CellBuffer::PerformRedoStep() {
+       const Action &actionStep = uh.GetRedoStep();
        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;
+                       styledData[i*2 + 1] = 0;
                }
-               BasicInsertString(actionStep.position, styledData, actionStep.lenData*2);
+               BasicInsertString(actionStep.position*2, styledData, actionStep.lenData*2);
                delete []styledData;
        } else if (actionStep.at == removeAction) {
-               BasicDeleteChars(actionStep.position, actionStep.lenData*2);
+               BasicDeleteChars(actionStep.position*2, actionStep.lenData*2);
        }
-       currentAction++;
-       return actionStep;
+       uh.CompletedRedoStep();
 }
 
 int CellBuffer::SetLineState(int line, int state) {
@@ -925,7 +1092,7 @@ int CellBuffer::GetLineState(int line) {
 int CellBuffer::GetMaxLineState() {
        return lineStates.Length();
 }
-               
+
 int CellBuffer::SetLevel(int line, int level) {
        int prev = 0;
        if ((line >= 0) && (line < lv.lines)) {
@@ -948,3 +1115,6 @@ int CellBuffer::GetLevel(int line) {
        }
 }
 
+void CellBuffer::ClearLevels() {
+       lv.ClearLevels();
+}