JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_CodeDocument.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
30{
31public:
34 const int lineLen,
35 const int numNewLineChars,
36 const int startInFile)
37 : line (startOfLine, endOfLine),
38 lineStartInFile (startInFile),
39 lineLength (lineLen),
40 lineLengthWithoutNewLines (lineLen - numNewLineChars)
41 {
42 }
43
44 static void createLines (Array<CodeDocumentLine*>& newLines, StringRef text)
45 {
46 auto t = text.text;
47 int charNumInFile = 0;
48 bool finished = false;
49
50 while (! (finished || t.isEmpty()))
51 {
52 auto startOfLine = t;
54 int lineLength = 0;
55 int numNewLineChars = 0;
56
57 for (;;)
58 {
59 auto c = t.getAndAdvance();
60
61 if (c == 0)
62 {
63 finished = true;
64 break;
65 }
66
68 ++lineLength;
69
70 if (c == '\r')
71 {
73
74 if (*t == '\n')
75 {
76 ++t;
78 ++lineLength;
80 }
81
82 break;
83 }
84
85 if (c == '\n')
86 {
88 break;
89 }
90 }
91
92 newLines.add (new CodeDocumentLine (startOfLine, t, lineLength,
94 }
95
96 jassert (charNumInFile == text.length());
97 }
98
99 bool endsWithLineBreak() const noexcept
100 {
101 return lineLengthWithoutNewLines != lineLength;
102 }
103
104 void updateLength() noexcept
105 {
106 lineLength = 0;
107 lineLengthWithoutNewLines = 0;
108
109 for (auto t = line.getCharPointer();;)
110 {
111 auto c = t.getAndAdvance();
112
113 if (c == 0)
114 break;
115
116 ++lineLength;
117
118 if (c != '\n' && c != '\r')
119 lineLengthWithoutNewLines = lineLength;
120 }
121 }
122
123 String line;
124 int lineStartInFile, lineLength, lineLengthWithoutNewLines;
125};
126
127//==============================================================================
129 : document (&doc)
130{}
131
132CodeDocument::Iterator::Iterator (CodeDocument::Position p) noexcept
133 : document (p.owner),
134 line (p.getLineNumber()),
135 position (p.getPosition())
136{
137 reinitialiseCharPtr();
138
139 for (int i = 0; i < p.getIndexInLine(); ++i)
140 {
141 charPointer.getAndAdvance();
142
143 if (charPointer.isEmpty())
144 {
145 position -= (p.getIndexInLine() - i);
146 break;
147 }
148 }
149}
150
152 : document (nullptr)
153{
154}
155
156CodeDocument::Iterator::~Iterator() noexcept {}
157
158
159bool CodeDocument::Iterator::reinitialiseCharPtr() const
160{
162 jassert (document != nullptr);
163
164 if (charPointer.getAddress() == nullptr)
165 {
166 if (auto* l = document->lines[line])
167 charPointer = l->line.getCharPointer();
168 else
169 return false;
170 }
171
172 return true;
173}
174
176{
177 for (;;)
178 {
179 if (! reinitialiseCharPtr())
180 return 0;
181
182 if (auto result = charPointer.getAndAdvance())
183 {
184 if (charPointer.isEmpty())
185 {
186 ++line;
187 charPointer = nullptr;
188 }
189
190 ++position;
191 return result;
192 }
193
194 ++line;
195 charPointer = nullptr;
196 }
197}
198
200{
201 nextChar();
202}
203
205{
206 if (! reinitialiseCharPtr())
207 return;
208
209 position += (int) charPointer.length();
210 ++line;
211 charPointer = nullptr;
212}
213
215{
216 if (! reinitialiseCharPtr())
217 return;
218
219 if (auto* l = document->lines [line])
220 {
221 auto startPtr = l->line.getCharPointer();
222 position -= (int) startPtr.lengthUpTo (charPointer);
223 charPointer = startPtr;
224 }
225}
226
228{
229 if (! reinitialiseCharPtr())
230 return 0;
231
232 if (auto c = *charPointer)
233 return c;
234
235 if (auto* l = document->lines [line + 1])
236 return l->line[0];
237
238 return 0;
239}
240
242{
243 if (! reinitialiseCharPtr())
244 return 0;
245
246 for (;;)
247 {
248 if (auto* l = document->lines[line])
249 {
250 if (charPointer != l->line.getCharPointer())
251 {
252 --position;
253 --charPointer;
254 break;
255 }
256 }
257
258 if (line == 0)
259 return 0;
260
261 --line;
262
263 if (auto* prev = document->lines[line])
264 charPointer = prev->line.getCharPointer().findTerminatingNull();
265 }
266
267 return *charPointer;
268}
269
271{
272 if (! reinitialiseCharPtr())
273 return 0;
274
275 if (auto* l = document->lines[line])
276 {
277 if (charPointer != l->line.getCharPointer())
278 return *(charPointer - 1);
279
280 if (auto* prev = document->lines[line - 1])
281 return *(prev->line.getCharPointer().findTerminatingNull() - 1);
282 }
283
284 return 0;
285}
286
288{
289 while (CharacterFunctions::isWhitespace (peekNextChar()))
290 skip();
291}
292
294{
295 return charPointer.getAddress() == nullptr && line >= document->lines.size();
296}
297
299{
300 return position == 0;
301}
302
304{
305 if (auto* l = document->lines[line])
306 {
307 reinitialiseCharPtr();
308 int indexInLine = 0;
309 auto linePtr = l->line.getCharPointer();
310
311 while (linePtr != charPointer && ! linePtr.isEmpty())
312 {
313 ++indexInLine;
314 ++linePtr;
315 }
316
317 return CodeDocument::Position (*document, line, indexInLine);
318 }
319
320 if (isEOF())
321 {
322 if (auto* last = document->lines.getLast())
323 {
324 auto lineIndex = document->lines.size() - 1;
325 return CodeDocument::Position (*document, lineIndex, last->lineLength);
326 }
327 }
328
329 return CodeDocument::Position (*document, 0, 0);
330}
331
332//==============================================================================
336
338 const int lineNum, const int index) noexcept
339 : owner (const_cast<CodeDocument*> (&ownerDocument)),
340 line (lineNum), indexInLine (index)
341{
342 setLineAndIndex (lineNum, index);
343}
344
346 : owner (const_cast<CodeDocument*> (&ownerDocument))
347{
348 setPosition (pos);
349}
350
352 : owner (other.owner), characterPos (other.characterPos), line (other.line),
353 indexInLine (other.indexInLine)
354{
355 jassert (*this == other);
356}
357
359{
360 setPositionMaintained (false);
361}
362
363CodeDocument::Position& CodeDocument::Position::operator= (const Position& other)
364{
365 if (this != &other)
366 {
367 const bool wasPositionMaintained = positionMaintained;
368 if (owner != other.owner)
369 setPositionMaintained (false);
370
371 owner = other.owner;
372 line = other.line;
373 indexInLine = other.indexInLine;
374 characterPos = other.characterPos;
375 setPositionMaintained (wasPositionMaintained);
376
377 jassert (*this == other);
378 }
379
380 return *this;
381}
382
383bool CodeDocument::Position::operator== (const Position& other) const noexcept
384{
385 jassert ((characterPos == other.characterPos)
386 == (line == other.line && indexInLine == other.indexInLine));
387
388 return characterPos == other.characterPos
389 && line == other.line
390 && indexInLine == other.indexInLine
391 && owner == other.owner;
392}
393
394bool CodeDocument::Position::operator!= (const Position& other) const noexcept
395{
396 return ! operator== (other);
397}
398
400{
401 jassert (owner != nullptr);
402
403 if (owner->lines.size() == 0)
404 {
405 line = 0;
406 indexInLine = 0;
407 characterPos = 0;
408 }
409 else
410 {
411 if (newLineNum >= owner->lines.size())
412 {
413 line = owner->lines.size() - 1;
414
415 auto& l = *owner->lines.getUnchecked (line);
416 indexInLine = l.lineLengthWithoutNewLines;
417 characterPos = l.lineStartInFile + indexInLine;
418 }
419 else
420 {
421 line = jmax (0, newLineNum);
422
423 auto& l = *owner->lines.getUnchecked (line);
424
425 if (l.lineLengthWithoutNewLines > 0)
426 indexInLine = jlimit (0, l.lineLengthWithoutNewLines, newIndexInLine);
427 else
428 indexInLine = 0;
429
430 characterPos = l.lineStartInFile + indexInLine;
431 }
432 }
433}
434
436{
437 jassert (owner != nullptr);
438
439 line = 0;
440 indexInLine = 0;
441 characterPos = 0;
442
443 if (newPosition > 0)
444 {
445 int lineStart = 0;
446 auto lineEnd = owner->lines.size();
447
448 for (;;)
449 {
450 if (lineEnd - lineStart < 4)
451 {
452 for (int i = lineStart; i < lineEnd; ++i)
453 {
454 auto& l = *owner->lines.getUnchecked (i);
455 auto index = newPosition - l.lineStartInFile;
456
457 if (index >= 0 && (index < l.lineLength || i == lineEnd - 1))
458 {
459 line = i;
460 indexInLine = jmin (l.lineLengthWithoutNewLines, index);
461 characterPos = l.lineStartInFile + indexInLine;
462 }
463 }
464
465 break;
466 }
467 else
468 {
469 auto midIndex = (lineStart + lineEnd + 1) / 2;
470
471 if (newPosition >= owner->lines.getUnchecked (midIndex)->lineStartInFile)
473 else
475 }
476 }
477 }
478}
479
481{
482 jassert (owner != nullptr);
483
484 if (characterDelta == 1)
485 {
486 setPosition (getPosition());
487
488 // If moving right, make sure we don't get stuck between the \r and \n characters..
489 if (line < owner->lines.size())
490 {
491 auto& l = *owner->lines.getUnchecked (line);
492
493 if (indexInLine + characterDelta < l.lineLength
494 && indexInLine + characterDelta >= l.lineLengthWithoutNewLines + 1)
496 }
497 }
498
499 setPosition (characterPos + characterDelta);
500}
501
508
510{
511 CodeDocument::Position p (*this);
512 p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine());
513 return p;
514}
515
517{
518 if (auto* l = owner->lines [line])
519 return l->line [getIndexInLine()];
520
521 return 0;
522}
523
525{
526 if (auto* l = owner->lines [line])
527 return l->line;
528
529 return {};
530}
531
533{
534 if (isMaintained != positionMaintained)
535 {
536 positionMaintained = isMaintained;
537
538 if (owner != nullptr)
539 {
540 if (isMaintained)
541 {
542 jassert (! owner->positionsToMaintain.contains (this));
543 owner->positionsToMaintain.add (this);
544 }
545 else
546 {
547 // If this happens, you may have deleted the document while there are Position objects that are still using it...
548 jassert (owner->positionsToMaintain.contains (this));
549 owner->positionsToMaintain.removeFirstMatchingValue (this);
550 }
551 }
552 }
553}
554
555//==============================================================================
556CodeDocument::CodeDocument() : undoManager (std::numeric_limits<int>::max(), 10000)
557{
558}
559
563
565{
566 return getTextBetween (Position (*this, 0),
567 Position (*this, lines.size(), 0));
568}
569
571{
572 if (end.getPosition() <= start.getPosition())
573 return {};
574
575 auto startLine = start.getLineNumber();
576 auto endLine = end.getLineNumber();
577
578 if (startLine == endLine)
579 {
580 if (auto* line = lines [startLine])
581 return line->line.substring (start.getIndexInLine(), end.getIndexInLine());
582
583 return {};
584 }
585
587 mo.preallocate ((size_t) (end.getPosition() - start.getPosition() + 4));
588
589 auto maxLine = jmin (lines.size() - 1, endLine);
590
591 for (int i = jmax (0, startLine); i <= maxLine; ++i)
592 {
593 auto& line = *lines.getUnchecked (i);
594 auto len = line.lineLength;
595
596 if (i == startLine)
597 {
598 auto index = start.getIndexInLine();
599 mo << line.line.substring (index, len);
600 }
601 else if (i == endLine)
602 {
603 len = end.getIndexInLine();
604 mo << line.line.substring (0, len);
605 }
606 else
607 {
608 mo << line.line;
609 }
610 }
611
612 return mo.toUTF8();
613}
614
616{
617 if (auto* lastLine = lines.getLast())
618 return lastLine->lineStartInFile + lastLine->lineLength;
619
620 return 0;
621}
622
623String CodeDocument::getLine (const int lineIndex) const noexcept
624{
625 if (auto* line = lines[lineIndex])
626 return line->line;
627
628 return {};
629}
630
632{
633 if (maximumLineLength < 0)
634 {
635 maximumLineLength = 0;
636
637 for (auto* l : lines)
638 maximumLineLength = jmax (maximumLineLength, l->lineLength);
639 }
640
641 return maximumLineLength;
642}
643
645{
646 deleteSection (startPosition.getPosition(), endPosition.getPosition());
647}
648
649void CodeDocument::deleteSection (const int start, const int end)
650{
651 remove (start, end, true);
652}
653
654void CodeDocument::insertText (const Position& position, const String& text)
655{
656 insertText (position.getPosition(), text);
657}
658
659void CodeDocument::insertText (const int insertIndex, const String& text)
660{
661 insert (text, insertIndex, true);
662}
663
664void CodeDocument::replaceSection (const int start, const int end, const String& newText)
665{
667 deleteSection (start, end);
668}
669
671{
673 .joinIntoString (newLineChars));
674
676
677 for (auto& c : diff.changes)
678 {
679 if (c.isDeletion())
680 remove (c.start, c.start + c.length, true);
681 else
682 insert (c.insertedText, c.start, true);
683 }
684}
685
687{
688 remove (0, getNumCharacters(), true);
689 insert (newContent, 0, true);
690}
691
693{
694 remove (0, getNumCharacters(), false);
695 insert (stream.readEntireStreamAsString(), 0, false);
696 setSavePoint();
698 return true;
699}
700
702{
703 for (auto* l : lines)
704 {
705 auto temp = l->line; // use a copy to avoid bloating the memory footprint of the stored string.
706 const char* utf8 = temp.toUTF8();
707
708 if (! stream.write (utf8, strlen (utf8)))
709 return false;
710 }
711
712 return true;
713}
714
716{
717 jassert (newChars == "\r\n" || newChars == "\n" || newChars == "\r");
718 newLineChars = newChars;
719}
720
722{
723 undoManager.beginNewTransaction (String());
724}
725
727{
729 undoManager.undo();
730}
731
733{
734 undoManager.redo();
735}
736
738{
739 undoManager.clearUndoHistory();
740}
741
743{
744 indexOfSavedState = currentActionIndex;
745}
746
748{
749 return currentActionIndex != indexOfSavedState;
750}
751
752//==============================================================================
753static int getCharacterType (juce_wchar character) noexcept
754{
755 return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
756 ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
757}
758
760{
761 auto p = position;
762 const int maxDistance = 256;
763 int i = 0;
764
765 while (i < maxDistance
766 && CharacterFunctions::isWhitespace (p.getCharacter())
767 && (i == 0 || (p.getCharacter() != '\n'
768 && p.getCharacter() != '\r')))
769 {
770 ++i;
771 p.moveBy (1);
772 }
773
774 if (i == 0)
775 {
776 auto type = getCharacterType (p.getCharacter());
777
778 while (i < maxDistance && type == getCharacterType (p.getCharacter()))
779 {
780 ++i;
781 p.moveBy (1);
782 }
783
784 while (i < maxDistance
785 && CharacterFunctions::isWhitespace (p.getCharacter())
786 && (i == 0 || (p.getCharacter() != '\n'
787 && p.getCharacter() != '\r')))
788 {
789 ++i;
790 p.moveBy (1);
791 }
792 }
793
794 return p;
795}
796
798{
799 auto p = position;
800 const int maxDistance = 256;
801 int i = 0;
802 bool stoppedAtLineStart = false;
803
804 while (i < maxDistance)
805 {
806 auto c = p.movedBy (-1).getCharacter();
807
808 if (c == '\r' || c == '\n')
809 {
810 stoppedAtLineStart = true;
811
812 if (i > 0)
813 break;
814 }
815
817 break;
818
819 p.moveBy (-1);
820 ++i;
821 }
822
823 if (i < maxDistance && ! stoppedAtLineStart)
824 {
825 auto type = getCharacterType (p.movedBy (-1).getCharacter());
826
827 while (i < maxDistance && type == getCharacterType (p.movedBy (-1).getCharacter()))
828 {
829 p.moveBy (-1);
830 ++i;
831 }
832 }
833
834 return p;
835}
836
837void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept
838{
839 auto isTokenCharacter = [] (juce_wchar c) { return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_'; };
840
841 end = pos;
842 while (isTokenCharacter (end.getCharacter()))
843 end.moveBy (1);
844
845 start = end;
846 while (start.getIndexInLine() > 0
847 && isTokenCharacter (start.movedBy (-1).getCharacter()))
848 start.moveBy (-1);
849}
850
851void CodeDocument::findLineContaining (const Position& pos, Position& s, Position& e) const noexcept
852{
853 s.setLineAndIndex (pos.getLineNumber(), 0);
854 e.setLineAndIndex (pos.getLineNumber() + 1, 0);
855}
856
857void CodeDocument::checkLastLineStatus()
858{
859 while (lines.size() > 0
860 && lines.getLast()->lineLength == 0
861 && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
862 {
863 // remove any empty lines at the end if the preceding line doesn't end in a newline.
864 lines.removeLast();
865 }
866
867 const CodeDocumentLine* const lastLine = lines.getLast();
868
869 if (lastLine != nullptr && lastLine->endsWithLineBreak())
870 {
871 // check that there's an empty line at the end if the preceding one ends in a newline..
872 lines.add (new CodeDocumentLine (StringRef(), StringRef(), 0, 0,
873 lastLine->lineStartInFile + lastLine->lineLength));
874 }
875}
876
877//==============================================================================
880
881//==============================================================================
883{
884 InsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
885 : owner (doc), text (t), insertPos (pos)
886 {
887 }
888
889 bool perform() override
890 {
891 owner.currentActionIndex++;
892 owner.insert (text, insertPos, false);
893 return true;
894 }
895
896 bool undo() override
897 {
898 owner.currentActionIndex--;
899 owner.remove (insertPos, insertPos + text.length(), false);
900 return true;
901 }
902
903 int getSizeInUnits() override { return text.length() + 32; }
904
905 CodeDocument& owner;
906 const String text;
907 const int insertPos;
908
910};
911
912void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
913{
914 if (text.isNotEmpty())
915 {
916 if (undoable)
917 {
918 undoManager.perform (new InsertAction (*this, text, insertPos));
919 }
920 else
921 {
922 Position pos (*this, insertPos);
923 auto firstAffectedLine = pos.getLineNumber();
924
925 auto* firstLine = lines[firstAffectedLine];
926 auto textInsideOriginalLine = text;
927
928 if (firstLine != nullptr)
929 {
930 auto index = pos.getIndexInLine();
931 textInsideOriginalLine = firstLine->line.substring (0, index)
933 + firstLine->line.substring (index);
934 }
935
936 maximumLineLength = -1;
938 CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
939 jassert (newLines.size() > 0);
940
941 auto* newFirstLine = newLines.getUnchecked (0);
942 newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
943 lines.set (firstAffectedLine, newFirstLine);
944
945 if (newLines.size() > 1)
946 lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);
947
948 int lineStart = newFirstLine->lineStartInFile;
949
950 for (int i = firstAffectedLine; i < lines.size(); ++i)
951 {
952 auto& l = *lines.getUnchecked (i);
953 l.lineStartInFile = lineStart;
954 lineStart += l.lineLength;
955 }
956
957 checkLastLineStatus();
958 auto newTextLength = text.length();
959
960 for (auto* p : positionsToMaintain)
961 if (p->getPosition() >= insertPos)
962 p->setPosition (p->getPosition() + newTextLength);
963
964 listeners.call ([&] (Listener& l) { l.codeDocumentTextInserted (text, insertPos); });
965 }
966 }
967}
968
969//==============================================================================
971{
972 DeleteAction (CodeDocument& doc, int start, int end) noexcept
973 : owner (doc), startPos (start), endPos (end),
974 removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
976 {
977 }
978
979 bool perform() override
980 {
981 owner.currentActionIndex++;
982 owner.remove (startPos, endPos, false);
983 return true;
984 }
985
986 bool undo() override
987 {
988 owner.currentActionIndex--;
989 owner.insert (removedText, startPos, false);
990 return true;
991 }
992
993 int getSizeInUnits() override { return (endPos - startPos) + 32; }
994
995 CodeDocument& owner;
996 const int startPos, endPos;
997 const String removedText;
998
1000};
1001
1002void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
1003{
1004 if (endPos <= startPos)
1005 return;
1006
1007 if (undoable)
1008 {
1009 undoManager.perform (new DeleteAction (*this, startPos, endPos));
1010 }
1011 else
1012 {
1013 Position startPosition (*this, startPos);
1014 Position endPosition (*this, endPos);
1015
1016 maximumLineLength = -1;
1017 auto firstAffectedLine = startPosition.getLineNumber();
1018 auto endLine = endPosition.getLineNumber();
1019 auto& firstLine = *lines.getUnchecked (firstAffectedLine);
1020
1022 {
1023 firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
1024 + firstLine.line.substring (endPosition.getIndexInLine());
1025 firstLine.updateLength();
1026 }
1027 else
1028 {
1029 auto& lastLine = *lines.getUnchecked (endLine);
1030
1031 firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
1032 + lastLine.line.substring (endPosition.getIndexInLine());
1033 firstLine.updateLength();
1034
1036 lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
1037 }
1038
1039 for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
1040 {
1041 auto& l = *lines.getUnchecked (i);
1042 auto& previousLine = *lines.getUnchecked (i - 1);
1043 l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
1044 }
1045
1046 checkLastLineStatus();
1048
1049 for (auto* p : positionsToMaintain)
1050 {
1051 if (p->getPosition() > startPosition.getPosition())
1052 p->setPosition (jmax (startPos, p->getPosition() + startPos - endPos));
1053
1054 if (p->getPosition() > totalChars)
1055 p->setPosition (totalChars);
1056 }
1057
1058 listeners.call ([=] (Listener& l) { l.codeDocumentTextDeleted (startPos, endPos); });
1059 }
1060}
1061
1062//==============================================================================
1063//==============================================================================
1064#if JUCE_UNIT_TESTS
1065
1066struct CodeDocumentTest final : public UnitTest
1067{
1069 : UnitTest ("CodeDocument", UnitTestCategories::text)
1070 {}
1071
1072 void runTest() override
1073 {
1074 const juce::String jabberwocky ("'Twas brillig, and the slithy toves\n"
1075 "Did gyre and gimble in the wabe;\n"
1076 "All mimsy were the borogoves,\n"
1077 "And the mome raths outgrabe.\n\n"
1078
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!'");
1083
1084 {
1085 beginTest ("Basic checks");
1086 CodeDocument d;
1087 d.replaceAllContent (jabberwocky);
1088
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"));
1093 }
1094
1095 {
1096 beginTest ("Insert/replace/delete");
1097
1098 CodeDocument d;
1099 d.replaceAllContent (jabberwocky);
1100
1101 d.insertText (CodeDocument::Position (d, 0, 6), "very ");
1102 expect (d.getLine (0).startsWith ("'Twas very brillig"),
1103 "Insert text within a line");
1104
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");
1108
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");
1112
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");
1116
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");
1120 }
1121
1122 {
1123 beginTest ("Line splitting and joining");
1124
1125 CodeDocument d;
1126 d.replaceAllContent (jabberwocky);
1127 expectEquals (d.getNumLines(), 9);
1128
1129 const String splitComment ("Adding a newline should split a line into two.");
1130 d.insertText (49, "\n");
1131
1132 expectEquals (d.getNumLines(), 10, splitComment);
1133 expectEquals (d.getLine (1), String ("Did gyre and \n"), splitComment);
1134 expectEquals (d.getLine (2), String ("gimble in the wabe;\n"), splitComment);
1135
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));
1139
1140 expectEquals (d.getNumLines(), 9, joinComment);
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);
1143 }
1144
1145 {
1146 beginTest ("Undo/redo");
1147
1148 CodeDocument d;
1149 d.replaceAllContent (jabberwocky);
1150 d.newTransaction();
1151 d.insertText (30, "INSERT1");
1152 d.newTransaction();
1153 d.insertText (70, "INSERT2");
1154 d.undo();
1155
1156 expect (d.getAllContent().contains ("INSERT1"), "1st edit should remain.");
1157 expect (! d.getAllContent().contains ("INSERT2"), "2nd edit should be undone.");
1158
1159 d.redo();
1160 expect (d.getAllContent().contains ("INSERT2"), "2nd edit should be redone.");
1161
1162 d.newTransaction();
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.");
1166 d.undo();
1167 expect (d.getAllContent().contains ("INSERT1"), "1st edit should be restored.");
1168 expect (d.getAllContent().contains ("INSERT2"), "1st edit should be restored.");
1169
1170 d.undo();
1171 d.undo();
1172 expectEquals (d.getAllContent(), jabberwocky, "Original document should be restored.");
1173 }
1174
1175 {
1176 beginTest ("Positions");
1177
1178 CodeDocument d;
1179 d.replaceAllContent (jabberwocky);
1180
1181 {
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);
1187 expectEquals (p1.getCharacter(), juce_wchar ('\''), comment);
1188 expectEquals (p2.getLineNumber(), 0, comment);
1189 expectEquals (p2.getIndexInLine(), 0, comment);
1190 expectEquals (p2.getCharacter(), juce_wchar ('\''), comment);
1191 }
1192
1193 {
1194 const String comment ("Moving by character handles newlines correctly.");
1195 CodeDocument::Position p1 (d, 0, 35);
1196 p1.moveBy (1);
1197 expectEquals (p1.getLineNumber(), 1, comment);
1198 expectEquals (p1.getIndexInLine(), 0, comment);
1199 p1.moveBy (75);
1200 expectEquals (p1.getLineNumber(), 3, comment);
1201 }
1202
1203 {
1204 const String comment1 ("setPositionMaintained tracks position.");
1205 const String comment2 ("setPositionMaintained tracks position following undos.");
1206
1207 CodeDocument::Position p1 (d, 3, 0);
1208 p1.setPositionMaintained (true);
1209 expectEquals (p1.getCharacter(), juce_wchar ('A'), comment1);
1210
1211 d.newTransaction();
1212 d.insertText (p1, "INSERT1");
1213
1214 expectEquals (p1.getCharacter(), juce_wchar ('A'), comment1);
1215 expectEquals (p1.getLineNumber(), 3, comment1);
1216 expectEquals (p1.getIndexInLine(), 7, comment1);
1217 d.undo();
1218 expectEquals (p1.getIndexInLine(), 0, comment2);
1219
1220 d.newTransaction();
1221 d.insertText (15, "\n");
1222
1223 expectEquals (p1.getLineNumber(), 4, comment1);
1224 d.undo();
1225 expectEquals (p1.getLineNumber(), 3, comment2);
1226 }
1227 }
1228
1229 {
1230 beginTest ("Iterators");
1231
1232 CodeDocument d;
1233 d.replaceAllContent (jabberwocky);
1234
1235 {
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.");
1240
1241 CodeDocument::Iterator it (d);
1242 expectEquals (it.peekNextChar(), juce_wchar ('\''), comment1);
1243 expectEquals (it.nextChar(), juce_wchar ('\''), comment1);
1244 expectEquals (it.nextChar(), juce_wchar ('T'), comment1);
1245 expectEquals (it.nextChar(), juce_wchar ('w'), comment1);
1246 expectEquals (it.peekNextChar(), juce_wchar ('a'), comment2);
1247 expectEquals (it.previousChar(), juce_wchar ('w'), comment2);
1248 expectEquals (it.previousChar(), juce_wchar ('T'), comment2);
1249 expectEquals (it.previousChar(), juce_wchar ('\''), comment2);
1250 expectEquals (it.previousChar(), juce_wchar (0), comment3);
1251 expect (it.isSOF(), comment3);
1252
1253 while (it.peekNextChar() != juce_wchar ('D')) // "Did gyre..."
1254 it.nextChar();
1255
1256 expectEquals (it.nextChar(), juce_wchar ('D'), comment3);
1257 expectEquals (it.peekNextChar(), juce_wchar ('i'), comment3);
1258 expectEquals (it.previousChar(), juce_wchar ('D'), comment3);
1259 expectEquals (it.previousChar(), juce_wchar ('\n'), comment3);
1260 expectEquals (it.previousChar(), juce_wchar ('s'), comment3);
1261 }
1262
1263 {
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.");
1267
1268 CodeDocument::Position p (d, 6, 0); // "The jaws..."
1269 CodeDocument::Iterator it (p);
1270
1271 expectEquals (it.nextChar(), juce_wchar ('T'), comment1);
1272 expectEquals (it.nextChar(), juce_wchar ('h'), comment1);
1273 expectEquals (it.previousChar(), juce_wchar ('h'), comment1);
1274 expectEquals (it.previousChar(), juce_wchar ('T'), comment1);
1275 expectEquals (it.previousChar(), juce_wchar ('\n'), comment1);
1276 expectEquals (it.previousChar(), juce_wchar ('!'), comment1);
1277
1278 const auto p2 = it.toPosition();
1279 expectEquals (p2.getLineNumber(), 5, comment2);
1280 expectEquals (p2.getIndexInLine(), 30, comment2);
1281
1282 while (! it.isEOF())
1283 it.nextChar();
1284
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);
1288 }
1289 }
1290 }
1291};
1292
1294
1295#endif
1296
1297} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
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.
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.
The base class for streams that read data.
virtual String readEntireStreamAsString()
Tries to read the whole stream and turn it into a string.
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.
The JUCE String class!
Definition juce_String.h:53
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.
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE(className)
This is a shorthand macro for deleting a class's copy constructor and copy assignment operator.
typedef int
JUCE Namespace.
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...
Definition juce_Memory.h:88
strlen
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.