48 bool finished =
false;
50 while (! (finished || t.isEmpty()))
59 auto c = t.getAndAdvance();
99 bool endsWithLineBreak()
const noexcept
101 return lineLengthWithoutNewLines != lineLength;
104 void updateLength()
noexcept
107 lineLengthWithoutNewLines = 0;
111 auto c = t.getAndAdvance();
118 if (c !=
'\n' && c !=
'\r')
119 lineLengthWithoutNewLines = lineLength;
124 int lineStartInFile, lineLength, lineLengthWithoutNewLines;
133 : document (p.owner),
134 line (p.getLineNumber()),
135 position (p.getPosition())
137 reinitialiseCharPtr();
139 for (
int i = 0; i < p.getIndexInLine(); ++i)
141 charPointer.getAndAdvance();
143 if (charPointer.isEmpty())
145 position -= (p.getIndexInLine() - i);
156CodeDocument::Iterator::~Iterator() noexcept {}
159bool CodeDocument::Iterator::reinitialiseCharPtr()
const
164 if (charPointer.getAddress() ==
nullptr)
166 if (
auto*
l = document->lines[line])
167 charPointer =
l->line.getCharPointer();
179 if (! reinitialiseCharPtr())
182 if (
auto result = charPointer.getAndAdvance())
184 if (charPointer.isEmpty())
187 charPointer =
nullptr;
195 charPointer =
nullptr;
206 if (! reinitialiseCharPtr())
209 position += (
int) charPointer.length();
211 charPointer =
nullptr;
216 if (! reinitialiseCharPtr())
219 if (
auto*
l = document->lines [line])
221 auto startPtr =
l->line.getCharPointer();
222 position -= (
int)
startPtr.lengthUpTo (charPointer);
229 if (! reinitialiseCharPtr())
232 if (
auto c = *charPointer)
235 if (
auto*
l = document->lines [line + 1])
243 if (! reinitialiseCharPtr())
248 if (
auto*
l = document->lines[line])
250 if (charPointer !=
l->line.getCharPointer())
263 if (
auto* prev = document->lines[line])
264 charPointer = prev->line.getCharPointer().findTerminatingNull();
272 if (! reinitialiseCharPtr())
275 if (
auto*
l = document->lines[line])
277 if (charPointer !=
l->line.getCharPointer())
278 return *(charPointer - 1);
280 if (
auto* prev = document->lines[line - 1])
281 return *(prev->line.getCharPointer().findTerminatingNull() - 1);
295 return charPointer.getAddress() ==
nullptr && line >= document->lines.size();
300 return position == 0;
305 if (
auto*
l = document->lines[line])
307 reinitialiseCharPtr();
309 auto linePtr =
l->line.getCharPointer();
322 if (
auto* last = document->lines.getLast())
324 auto lineIndex = document->lines.size() - 1;
338 const int lineNum,
const int index) noexcept
340 line (
lineNum), indexInLine (index)
342 setLineAndIndex (
lineNum, index);
352 : owner (
other.owner), characterPos (
other.characterPos), line (
other.line),
353 indexInLine (
other.indexInLine)
360 setPositionMaintained (
false);
368 if (owner !=
other.owner)
369 setPositionMaintained (
false);
373 indexInLine =
other.indexInLine;
374 characterPos =
other.characterPos;
383bool CodeDocument::Position::operator== (
const Position&
other)
const noexcept
386 == (line ==
other.line && indexInLine ==
other.indexInLine));
388 return characterPos ==
other.characterPos
389 && line ==
other.line
390 && indexInLine ==
other.indexInLine
391 && owner ==
other.owner;
394bool CodeDocument::Position::operator!= (
const Position&
other)
const noexcept
396 return ! operator== (
other);
403 if (owner->lines.size() == 0)
413 line = owner->lines.size() - 1;
415 auto&
l = *owner->lines.getUnchecked (line);
416 indexInLine =
l.lineLengthWithoutNewLines;
417 characterPos =
l.lineStartInFile + indexInLine;
423 auto&
l = *owner->lines.getUnchecked (line);
425 if (
l.lineLengthWithoutNewLines > 0)
430 characterPos =
l.lineStartInFile + indexInLine;
446 auto lineEnd = owner->lines.size();
454 auto&
l = *owner->lines.getUnchecked (i);
457 if (index >= 0 && (index <
l.lineLength || i ==
lineEnd - 1))
460 indexInLine =
jmin (
l.lineLengthWithoutNewLines, index);
461 characterPos =
l.lineStartInFile + indexInLine;
486 setPosition (getPosition());
491 auto&
l = *owner->lines.getUnchecked (line);
518 if (
auto*
l = owner->lines [line])
519 return l->line [getIndexInLine()];
526 if (
auto*
l = owner->lines [line])
538 if (owner !=
nullptr)
542 jassert (! owner->positionsToMaintain.contains (
this));
543 owner->positionsToMaintain.add (
this);
548 jassert (owner->positionsToMaintain.contains (
this));
549 owner->positionsToMaintain.removeFirstMatchingValue (
this);
593 auto& line = *lines.getUnchecked (i);
594 auto len = line.lineLength;
599 mo << line.line.substring (index, len);
603 len =
end.getIndexInLine();
604 mo << line.line.substring (0, len);
617 if (
auto*
lastLine = lines.getLast())
633 if (maximumLineLength < 0)
635 maximumLineLength = 0;
637 for (
auto*
l : lines)
638 maximumLineLength =
jmax (maximumLineLength,
l->lineLength);
641 return maximumLineLength;
651 remove (start,
end,
true);
661 insert (text, insertIndex,
true);
673 .joinIntoString (newLineChars));
677 for (
auto& c :
diff.changes)
680 remove (c.start, c.start + c.length,
true);
682 insert (c.insertedText, c.start,
true);
703 for (
auto*
l : lines)
706 const char*
utf8 = temp.toUTF8();
744 indexOfSavedState = currentActionIndex;
749 return currentActionIndex != indexOfSavedState;
753static int getCharacterType (
juce_wchar character)
noexcept
767 && (i == 0 || (p.getCharacter() !=
'\n'
768 && p.getCharacter() !=
'\r')))
776 auto type = getCharacterType (p.getCharacter());
778 while (i <
maxDistance && type == getCharacterType (p.getCharacter()))
786 && (i == 0 || (p.getCharacter() !=
'\n'
787 && p.getCharacter() !=
'\r')))
808 if (c ==
'\r' || c ==
'\n')
825 auto type = getCharacterType (p.movedBy (-1).getCharacter());
827 while (i <
maxDistance && type == getCharacterType (p.movedBy (-1).getCharacter()))
846 while (start.getIndexInLine() > 0
853 s.setLineAndIndex (pos.getLineNumber(), 0);
854 e.setLineAndIndex (pos.getLineNumber() + 1, 0);
857void CodeDocument::checkLastLineStatus()
859 while (lines.size() > 0
860 && lines.getLast()->lineLength == 0
861 && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
867 const CodeDocumentLine*
const lastLine = lines.getLast();
872 lines.add (
new CodeDocumentLine (StringRef(), StringRef(), 0, 0,
885 : owner (
doc), text (t), insertPos (pos)
891 owner.currentActionIndex++;
892 owner.insert (text, insertPos,
false);
898 owner.currentActionIndex--;
899 owner.remove (insertPos, insertPos + text.
length(),
false);
912void CodeDocument::insert (
const String& text,
const int insertPos,
const bool undoable)
918 undoManager.
perform (
new InsertAction (*
this, text, insertPos));
922 Position pos (*
this, insertPos);
928 if (firstLine !=
nullptr)
930 auto index = pos.getIndexInLine();
936 maximumLineLength = -1;
942 newFirstLine->lineStartInFile = firstLine !=
nullptr ? firstLine->lineStartInFile : 0;
952 auto&
l = *lines.getUnchecked (i);
957 checkLastLineStatus();
960 for (
auto* p : positionsToMaintain)
961 if (p->getPosition() >= insertPos)
964 listeners.call ([&] (Listener&
l) {
l.codeDocumentTextInserted (text, insertPos); });
973 : owner (
doc), startPos (start), endPos (
end),
981 owner.currentActionIndex++;
982 owner.remove (startPos, endPos,
false);
988 owner.currentActionIndex--;
989 owner.insert (removedText, startPos,
false);
996 const int startPos, endPos;
1002void CodeDocument::remove (
const int startPos,
const int endPos,
const bool undoable)
1004 if (endPos <= startPos)
1009 undoManager.
perform (
new DeleteAction (*
this, startPos, endPos));
1016 maximumLineLength = -1;
1023 firstLine.line = firstLine.line.substring (0,
startPosition.getIndexInLine())
1024 + firstLine.line.substring (
endPosition.getIndexInLine());
1025 firstLine.updateLength();
1031 firstLine.line = firstLine.line.substring (0,
startPosition.getIndexInLine())
1033 firstLine.updateLength();
1041 auto&
l = *lines.getUnchecked (i);
1046 checkLastLineStatus();
1049 for (
auto* p : positionsToMaintain)
1052 p->setPosition (
jmax (startPos, p->getPosition() + startPos - endPos));
1058 listeners.call ([=] (Listener&
l) {
l.codeDocumentTextDeleted (startPos, endPos); });
1072 void runTest()
override
1075 "Did gyre and gimble in the wabe;\n"
1076 "All mimsy were the borogoves,\n"
1077 "And the mome raths outgrabe.\n\n"
1079 "'Beware the Jabberwock, my son!\n"
1080 "The jaws that bite, the claws that catch!\n"
1081 "Beware the Jubjub bird, and shun\n"
1082 "The frumious Bandersnatch!'");
1085 beginTest (
"Basic checks");
1089 expectEquals (d.getNumLines(), 9);
1090 expect (d.getLine (0).startsWith (
"'Twas brillig"));
1091 expect (d.getLine (2).startsWith (
"All mimsy"));
1092 expectEquals (d.getLine (4), String (
"\n"));
1096 beginTest (
"Insert/replace/delete");
1101 d.insertText (CodeDocument::Position (d, 0, 6),
"very ");
1102 expect (d.getLine (0).startsWith (
"'Twas very brillig"),
1103 "Insert text within a line");
1105 d.replaceSection (74, 83,
"Quite hungry");
1106 expectEquals (d.getLine (2), String (
"Quite hungry were the borogoves,\n"),
1107 "Replace section at start of line");
1109 d.replaceSection (11, 18,
"cold");
1110 expectEquals (d.getLine (0), String (
"'Twas very cold, and the slithy toves\n"),
1111 "Replace section within a line");
1113 d.deleteSection (CodeDocument::Position (d, 2, 0), CodeDocument::Position (d, 2, 6));
1114 expectEquals (d.getLine (2), String (
"hungry were the borogoves,\n"),
1115 "Delete section within a line");
1117 d.deleteSection (CodeDocument::Position (d, 2, 6), CodeDocument::Position (d, 5, 11));
1118 expectEquals (d.getLine (2), String (
"hungry Jabberwock, my son!\n"),
1119 "Delete section across multiple line");
1123 beginTest (
"Line splitting and joining");
1127 expectEquals (d.getNumLines(), 9);
1129 const String
splitComment (
"Adding a newline should split a line into two.");
1130 d.insertText (49,
"\n");
1133 expectEquals (d.getLine (1), String (
"Did gyre and \n"),
splitComment);
1134 expectEquals (d.getLine (2), String (
"gimble in the wabe;\n"),
splitComment);
1136 const String
joinComment (
"Removing a newline should join two lines.");
1137 d.deleteSection (CodeDocument::Position (d, 0, 35),
1138 CodeDocument::Position (d, 1, 0));
1141 expectEquals (d.getLine (0), String (
"'Twas brillig, and the slithy tovesDid gyre and \n"),
joinComment);
1142 expectEquals (d.getLine (1), String (
"gimble in the wabe;\n"),
joinComment);
1146 beginTest (
"Undo/redo");
1151 d.insertText (30,
"INSERT1");
1153 d.insertText (70,
"INSERT2");
1156 expect (d.getAllContent().contains (
"INSERT1"),
"1st edit should remain.");
1157 expect (! d.getAllContent().contains (
"INSERT2"),
"2nd edit should be undone.");
1160 expect (d.getAllContent().contains (
"INSERT2"),
"2nd edit should be redone.");
1163 d.deleteSection (25, 90);
1164 expect (! d.getAllContent().contains (
"INSERT1"),
"1st edit should be deleted.");
1165 expect (! d.getAllContent().contains (
"INSERT2"),
"2nd edit should be deleted.");
1167 expect (d.getAllContent().contains (
"INSERT1"),
"1st edit should be restored.");
1168 expect (d.getAllContent().contains (
"INSERT2"),
"1st edit should be restored.");
1172 expectEquals (d.getAllContent(),
jabberwocky,
"Original document should be restored.");
1176 beginTest (
"Positions");
1182 const String
comment (
"Keeps negative positions inside document.");
1183 CodeDocument::Position
p1 (d, 0, -3);
1184 CodeDocument::Position
p2 (d, -8);
1185 expectEquals (
p1.getLineNumber(), 0,
comment);
1186 expectEquals (
p1.getIndexInLine(), 0,
comment);
1188 expectEquals (
p2.getLineNumber(), 0,
comment);
1189 expectEquals (
p2.getIndexInLine(), 0,
comment);
1194 const String
comment (
"Moving by character handles newlines correctly.");
1195 CodeDocument::Position
p1 (d, 0, 35);
1197 expectEquals (
p1.getLineNumber(), 1,
comment);
1198 expectEquals (
p1.getIndexInLine(), 0,
comment);
1200 expectEquals (
p1.getLineNumber(), 3,
comment);
1204 const String
comment1 (
"setPositionMaintained tracks position.");
1205 const String
comment2 (
"setPositionMaintained tracks position following undos.");
1207 CodeDocument::Position
p1 (d, 3, 0);
1208 p1.setPositionMaintained (
true);
1212 d.insertText (
p1,
"INSERT1");
1215 expectEquals (
p1.getLineNumber(), 3,
comment1);
1216 expectEquals (
p1.getIndexInLine(), 7,
comment1);
1218 expectEquals (
p1.getIndexInLine(), 0,
comment2);
1221 d.insertText (15,
"\n");
1223 expectEquals (
p1.getLineNumber(), 4,
comment1);
1225 expectEquals (
p1.getLineNumber(), 3,
comment2);
1230 beginTest (
"Iterators");
1236 const String
comment1 (
"Basic iteration.");
1237 const String
comment2 (
"Reverse iteration.");
1238 const String
comment3 (
"Reverse iteration stops at doc start.");
1239 const String
comment4 (
"Check iteration across line boundaries.");
1241 CodeDocument::Iterator
it (d);
1264 const String
comment1 (
"Iterator created from CodeDocument::Position objects.");
1265 const String
comment2 (
"CodeDocument::Position created from Iterator objects.");
1266 const String
comment3 (
"CodeDocument::Position created from EOF Iterator objects.");
1268 CodeDocument::Position p (d, 6, 0);
1269 CodeDocument::Iterator
it (p);
1278 const auto p2 =
it.toPosition();
1279 expectEquals (
p2.getLineNumber(), 5,
comment2);
1280 expectEquals (
p2.getIndexInLine(), 30,
comment2);
1282 while (!
it.isEOF())
1285 const auto p3 =
it.toPosition();
1286 expectEquals (
p3.getLineNumber(), d.getNumLines() - 1,
comment3);
1287 expectEquals (
p3.getIndexInLine(), d.getLine (d.getNumLines() - 1).length(),
comment3);
Holds a resizable array of primitive or copy-by-value objects.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...
static bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
static bool isLetterOrDigit(char character) noexcept
Checks whether a character is alphabetic or numeric.
void skip() noexcept
Advances the position by one character.
juce_wchar peekNextChar() const noexcept
Reads the next character without moving the current position.
bool isEOF() const noexcept
Returns true if the iterator has reached the end of the document.
CodeDocument::Position toPosition() const
Convert this iterator to a CodeDocument::Position.
Iterator() noexcept
Creates an uninitialised iterator.
bool isSOF() const noexcept
Returns true if the iterator is at the start of the document.
juce_wchar nextChar() noexcept
Reads the next character and returns it.
void skipToEndOfLine() noexcept
Skips forward until the next character will be the first character on the next line.
void skipToStartOfLine() noexcept
Skips backward until the next character will be the first character on this line.
juce_wchar peekPreviousChar() const noexcept
Reads the next character without moving the current position.
juce_wchar previousChar() noexcept
Reads the previous character and returns it.
void skipWhitespace() noexcept
Skips over any whitespace characters until the next character is non-whitespace.
An object that receives callbacks from the CodeDocument when its text changes.
A position in a code document.
juce_wchar getCharacter() const
Returns the character in the document at this position.
int getIndexInLine() const noexcept
Returns the number of characters from the start of the line.
int getPosition() const noexcept
Returns the position as the number of characters from the start of the document.
int getLineNumber() const noexcept
Returns the line number of this position.
void setLineAndIndex(int newLineNumber, int newIndexInLine)
Moves the position to a new line and index within the line.
void moveBy(int characterDelta)
Moves the position forwards or backwards by the specified number of characters.
void setPositionMaintained(bool isMaintained)
Allows the position to be automatically updated when the document changes.
Position movedByLines(int deltaLines) const
Returns a position which is the same as this one, moved up or down by the specified number of lines.
Position movedBy(int characterDelta) const
Returns a position which is the same as this one, moved by the specified number of characters.
Position() noexcept
Creates an uninitialised position.
void setPosition(int charactersFromStartOfDocument)
Points this object at a new position within the document.
String getLineText() const
Returns the line from the document that this position is within.
A class for storing and manipulating a source code file.
void undo()
Undo the last operation.
Position findWordBreakBefore(const Position &position) const noexcept
Searches for a word-break.
void addListener(Listener *listener)
Registers a listener object to receive callbacks when the document changes.
String getAllContent() const
Returns the full text of the document.
int getNumCharacters() const noexcept
Returns the number of characters in the document.
void replaceAllContent(const String &newContent)
Clears the document and replaces it with some new text.
String getLine(int lineIndex) const noexcept
Returns a line from the document.
void clearUndoHistory()
Clears the undo history.
void newTransaction()
Begins a new undo transaction.
void setNewLineCharacters(const String &newLineCharacters) noexcept
Sets the new-line characters that the document should use.
void replaceSection(int startIndex, int endIndex, const String &newText)
Replaces a section of the text with a new string.
void applyChanges(const String &newContent)
Analyses the changes between the current content and some new text, and applies those changes.
Position findWordBreakAfter(const Position &position) const noexcept
Searches for a word-break.
void findLineContaining(const Position &pos, Position &start, Position &end) const noexcept
Finds the line that contains the given position.
void removeListener(Listener *listener)
Deregisters a listener.
void insertText(const Position &position, const String &text)
Inserts some text into the document at a given position.
CodeDocument()
Creates a new, empty document.
void deleteSection(const Position &startPosition, const Position &endPosition)
Deletes a section of the text.
~CodeDocument()
Destructor.
int getMaximumLineLength() noexcept
Returns the number of characters in the longest line of the document.
bool loadFromStream(InputStream &stream)
Replaces the editor's contents with the contents of a stream.
bool writeToStream(OutputStream &stream)
Writes the editor's current contents to a stream.
void redo()
Redo the last operation.
bool hasChangedSinceSavePoint() const noexcept
Returns true if the state of the document differs from the state it was in when setSavePoint() was la...
void findTokenContaining(const Position &pos, Position &start, Position &end) const noexcept
Finds the token that contains the given position.
void setSavePoint() noexcept
Makes a note that the document's current state matches the one that is saved.
String getTextBetween(const Position &start, const Position &end) const
Returns a section of the document's text.
Writes data to an internal memory buffer, which grows as required.
void preallocate(size_t bytesToPreallocate)
Increases the internal storage capacity to be able to contain at least the specified amount of data w...
The base class for streams that write data to some kind of destination.
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
Writes a block of data to the stream.
static StringArray fromLines(StringRef stringToBreakUp)
Returns an array containing the lines in a given string.
A simple class for holding temporary references to a string literal or String.
int length() const noexcept
Returns the number of characters in the string.
String::CharPointerType text
The text that is referenced.
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
int length() const noexcept
Returns the number of characters in the string.
String substring(int startIndex, int endIndex) const
Returns a subsection of the string.
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
Calculates and applies a sequence of changes to convert one text string into another.
void beginNewTransaction()
Starts a new group of actions that together will be treated as a single transaction.
bool redo()
Tries to redo the last transaction that was undone.
bool undo()
Tries to roll-back the last transaction.
void clearUndoHistory()
Deletes all stored actions in the list.
bool perform(UndoableAction *action)
Performs an action and adds it to the undo history list.
Used by the UndoManager class to store an action which can be done and undone.
wchar_t juce_wchar
A platform-independent 32-bit unicode character type.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
bool perform() override
Overridden by a subclass to perform the action.
bool undo() override
Overridden by a subclass to undo the action.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
bool undo() override
Overridden by a subclass to undo the action.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
bool perform() override
Overridden by a subclass to perform the action.