The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

991 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class CodeDocumentLine
  18. {
  19. public:
  20. CodeDocumentLine (const String::CharPointerType startOfLine,
  21. const String::CharPointerType endOfLine,
  22. const int lineLen,
  23. const int numNewLineChars,
  24. const int startInFile)
  25. : line (startOfLine, endOfLine),
  26. lineStartInFile (startInFile),
  27. lineLength (lineLen),
  28. lineLengthWithoutNewLines (lineLen - numNewLineChars)
  29. {
  30. }
  31. static void createLines (Array<CodeDocumentLine*>& newLines, StringRef text)
  32. {
  33. String::CharPointerType t (text.text);
  34. int charNumInFile = 0;
  35. bool finished = false;
  36. while (! (finished || t.isEmpty()))
  37. {
  38. String::CharPointerType startOfLine (t);
  39. int startOfLineInFile = charNumInFile;
  40. int lineLength = 0;
  41. int numNewLineChars = 0;
  42. for (;;)
  43. {
  44. const juce_wchar c = t.getAndAdvance();
  45. if (c == 0)
  46. {
  47. finished = true;
  48. break;
  49. }
  50. ++charNumInFile;
  51. ++lineLength;
  52. if (c == '\r')
  53. {
  54. ++numNewLineChars;
  55. if (*t == '\n')
  56. {
  57. ++t;
  58. ++charNumInFile;
  59. ++lineLength;
  60. ++numNewLineChars;
  61. }
  62. break;
  63. }
  64. if (c == '\n')
  65. {
  66. ++numNewLineChars;
  67. break;
  68. }
  69. }
  70. newLines.add (new CodeDocumentLine (startOfLine, t, lineLength,
  71. numNewLineChars, startOfLineInFile));
  72. }
  73. jassert (charNumInFile == text.length());
  74. }
  75. bool endsWithLineBreak() const noexcept
  76. {
  77. return lineLengthWithoutNewLines != lineLength;
  78. }
  79. void updateLength() noexcept
  80. {
  81. lineLength = 0;
  82. lineLengthWithoutNewLines = 0;
  83. for (String::CharPointerType t (line.getCharPointer());;)
  84. {
  85. const juce_wchar c = t.getAndAdvance();
  86. if (c == 0)
  87. break;
  88. ++lineLength;
  89. if (c != '\n' && c != '\r')
  90. lineLengthWithoutNewLines = lineLength;
  91. }
  92. }
  93. String line;
  94. int lineStartInFile, lineLength, lineLengthWithoutNewLines;
  95. };
  96. //==============================================================================
  97. CodeDocument::Iterator::Iterator (const CodeDocument& doc) noexcept
  98. : document (&doc),
  99. charPointer (nullptr),
  100. line (0),
  101. position (0)
  102. {
  103. }
  104. CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) noexcept
  105. : document (other.document),
  106. charPointer (other.charPointer),
  107. line (other.line),
  108. position (other.position)
  109. {
  110. }
  111. CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) noexcept
  112. {
  113. document = other.document;
  114. charPointer = other.charPointer;
  115. line = other.line;
  116. position = other.position;
  117. return *this;
  118. }
  119. CodeDocument::Iterator::~Iterator() noexcept
  120. {
  121. }
  122. juce_wchar CodeDocument::Iterator::nextChar() noexcept
  123. {
  124. for (;;)
  125. {
  126. if (charPointer.getAddress() == nullptr)
  127. {
  128. if (const CodeDocumentLine* const l = document->lines[line])
  129. charPointer = l->line.getCharPointer();
  130. else
  131. return 0;
  132. }
  133. if (const juce_wchar result = charPointer.getAndAdvance())
  134. {
  135. if (charPointer.isEmpty())
  136. {
  137. ++line;
  138. charPointer = nullptr;
  139. }
  140. ++position;
  141. return result;
  142. }
  143. ++line;
  144. charPointer = nullptr;
  145. }
  146. }
  147. void CodeDocument::Iterator::skip() noexcept
  148. {
  149. nextChar();
  150. }
  151. void CodeDocument::Iterator::skipToEndOfLine() noexcept
  152. {
  153. if (charPointer.getAddress() == nullptr)
  154. {
  155. const CodeDocumentLine* const l = document->lines[line];
  156. if (l == nullptr)
  157. return;
  158. charPointer = l->line.getCharPointer();
  159. }
  160. position += (int) charPointer.length();
  161. ++line;
  162. charPointer = nullptr;
  163. }
  164. juce_wchar CodeDocument::Iterator::peekNextChar() const noexcept
  165. {
  166. if (charPointer.getAddress() == nullptr)
  167. {
  168. if (const CodeDocumentLine* const l = document->lines[line])
  169. charPointer = l->line.getCharPointer();
  170. else
  171. return 0;
  172. }
  173. if (const juce_wchar c = *charPointer)
  174. return c;
  175. if (const CodeDocumentLine* const l = document->lines [line + 1])
  176. return l->line[0];
  177. return 0;
  178. }
  179. void CodeDocument::Iterator::skipWhitespace() noexcept
  180. {
  181. while (CharacterFunctions::isWhitespace (peekNextChar()))
  182. skip();
  183. }
  184. bool CodeDocument::Iterator::isEOF() const noexcept
  185. {
  186. return charPointer.getAddress() == nullptr && line >= document->lines.size();
  187. }
  188. //==============================================================================
  189. CodeDocument::Position::Position() noexcept
  190. : owner (nullptr), characterPos (0), line (0),
  191. indexInLine (0), positionMaintained (false)
  192. {
  193. }
  194. CodeDocument::Position::Position (const CodeDocument& ownerDocument,
  195. const int lineNum, const int index) noexcept
  196. : owner (const_cast<CodeDocument*> (&ownerDocument)),
  197. characterPos (0), line (lineNum),
  198. indexInLine (index), positionMaintained (false)
  199. {
  200. setLineAndIndex (lineNum, index);
  201. }
  202. CodeDocument::Position::Position (const CodeDocument& ownerDocument, const int characterPos_) noexcept
  203. : owner (const_cast<CodeDocument*> (&ownerDocument)),
  204. positionMaintained (false)
  205. {
  206. setPosition (characterPos_);
  207. }
  208. CodeDocument::Position::Position (const Position& other) noexcept
  209. : owner (other.owner), characterPos (other.characterPos), line (other.line),
  210. indexInLine (other.indexInLine), positionMaintained (false)
  211. {
  212. jassert (*this == other);
  213. }
  214. CodeDocument::Position::~Position()
  215. {
  216. setPositionMaintained (false);
  217. }
  218. CodeDocument::Position& CodeDocument::Position::operator= (const Position& other)
  219. {
  220. if (this != &other)
  221. {
  222. const bool wasPositionMaintained = positionMaintained;
  223. if (owner != other.owner)
  224. setPositionMaintained (false);
  225. owner = other.owner;
  226. line = other.line;
  227. indexInLine = other.indexInLine;
  228. characterPos = other.characterPos;
  229. setPositionMaintained (wasPositionMaintained);
  230. jassert (*this == other);
  231. }
  232. return *this;
  233. }
  234. bool CodeDocument::Position::operator== (const Position& other) const noexcept
  235. {
  236. jassert ((characterPos == other.characterPos)
  237. == (line == other.line && indexInLine == other.indexInLine));
  238. return characterPos == other.characterPos
  239. && line == other.line
  240. && indexInLine == other.indexInLine
  241. && owner == other.owner;
  242. }
  243. bool CodeDocument::Position::operator!= (const Position& other) const noexcept
  244. {
  245. return ! operator== (other);
  246. }
  247. void CodeDocument::Position::setLineAndIndex (const int newLineNum, const int newIndexInLine)
  248. {
  249. jassert (owner != nullptr);
  250. if (owner->lines.size() == 0)
  251. {
  252. line = 0;
  253. indexInLine = 0;
  254. characterPos = 0;
  255. }
  256. else
  257. {
  258. if (newLineNum >= owner->lines.size())
  259. {
  260. line = owner->lines.size() - 1;
  261. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  262. indexInLine = l.lineLengthWithoutNewLines;
  263. characterPos = l.lineStartInFile + indexInLine;
  264. }
  265. else
  266. {
  267. line = jmax (0, newLineNum);
  268. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  269. if (l.lineLengthWithoutNewLines > 0)
  270. indexInLine = jlimit (0, l.lineLengthWithoutNewLines, newIndexInLine);
  271. else
  272. indexInLine = 0;
  273. characterPos = l.lineStartInFile + indexInLine;
  274. }
  275. }
  276. }
  277. void CodeDocument::Position::setPosition (const int newPosition)
  278. {
  279. jassert (owner != nullptr);
  280. line = 0;
  281. indexInLine = 0;
  282. characterPos = 0;
  283. if (newPosition > 0)
  284. {
  285. int lineStart = 0;
  286. int lineEnd = owner->lines.size();
  287. for (;;)
  288. {
  289. if (lineEnd - lineStart < 4)
  290. {
  291. for (int i = lineStart; i < lineEnd; ++i)
  292. {
  293. const CodeDocumentLine& l = *owner->lines.getUnchecked (i);
  294. const int index = newPosition - l.lineStartInFile;
  295. if (index >= 0 && (index < l.lineLength || i == lineEnd - 1))
  296. {
  297. line = i;
  298. indexInLine = jmin (l.lineLengthWithoutNewLines, index);
  299. characterPos = l.lineStartInFile + indexInLine;
  300. }
  301. }
  302. break;
  303. }
  304. else
  305. {
  306. const int midIndex = (lineStart + lineEnd + 1) / 2;
  307. if (newPosition >= owner->lines.getUnchecked (midIndex)->lineStartInFile)
  308. lineStart = midIndex;
  309. else
  310. lineEnd = midIndex;
  311. }
  312. }
  313. }
  314. }
  315. void CodeDocument::Position::moveBy (int characterDelta)
  316. {
  317. jassert (owner != nullptr);
  318. if (characterDelta == 1)
  319. {
  320. setPosition (getPosition());
  321. // If moving right, make sure we don't get stuck between the \r and \n characters..
  322. if (line < owner->lines.size())
  323. {
  324. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  325. if (indexInLine + characterDelta < l.lineLength
  326. && indexInLine + characterDelta >= l.lineLengthWithoutNewLines + 1)
  327. ++characterDelta;
  328. }
  329. }
  330. setPosition (characterPos + characterDelta);
  331. }
  332. CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const
  333. {
  334. CodeDocument::Position p (*this);
  335. p.moveBy (characterDelta);
  336. return p;
  337. }
  338. CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const
  339. {
  340. CodeDocument::Position p (*this);
  341. p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine());
  342. return p;
  343. }
  344. juce_wchar CodeDocument::Position::getCharacter() const
  345. {
  346. if (const CodeDocumentLine* const l = owner->lines [line])
  347. return l->line [getIndexInLine()];
  348. return 0;
  349. }
  350. String CodeDocument::Position::getLineText() const
  351. {
  352. if (const CodeDocumentLine* const l = owner->lines [line])
  353. return l->line;
  354. return String();
  355. }
  356. void CodeDocument::Position::setPositionMaintained (const bool isMaintained)
  357. {
  358. if (isMaintained != positionMaintained)
  359. {
  360. positionMaintained = isMaintained;
  361. if (owner != nullptr)
  362. {
  363. if (isMaintained)
  364. {
  365. jassert (! owner->positionsToMaintain.contains (this));
  366. owner->positionsToMaintain.add (this);
  367. }
  368. else
  369. {
  370. // If this happens, you may have deleted the document while there are Position objects that are still using it...
  371. jassert (owner->positionsToMaintain.contains (this));
  372. owner->positionsToMaintain.removeFirstMatchingValue (this);
  373. }
  374. }
  375. }
  376. }
  377. //==============================================================================
  378. CodeDocument::CodeDocument()
  379. : undoManager (std::numeric_limits<int>::max(), 10000),
  380. currentActionIndex (0),
  381. indexOfSavedState (-1),
  382. maximumLineLength (-1),
  383. newLineChars ("\r\n")
  384. {
  385. }
  386. CodeDocument::~CodeDocument()
  387. {
  388. }
  389. String CodeDocument::getAllContent() const
  390. {
  391. return getTextBetween (Position (*this, 0),
  392. Position (*this, lines.size(), 0));
  393. }
  394. String CodeDocument::getTextBetween (const Position& start, const Position& end) const
  395. {
  396. if (end.getPosition() <= start.getPosition())
  397. return String();
  398. const int startLine = start.getLineNumber();
  399. const int endLine = end.getLineNumber();
  400. if (startLine == endLine)
  401. {
  402. if (CodeDocumentLine* const line = lines [startLine])
  403. return line->line.substring (start.getIndexInLine(), end.getIndexInLine());
  404. return String();
  405. }
  406. MemoryOutputStream mo;
  407. mo.preallocate ((size_t) (end.getPosition() - start.getPosition() + 4));
  408. const int maxLine = jmin (lines.size() - 1, endLine);
  409. for (int i = jmax (0, startLine); i <= maxLine; ++i)
  410. {
  411. const CodeDocumentLine& line = *lines.getUnchecked(i);
  412. int len = line.lineLength;
  413. if (i == startLine)
  414. {
  415. const int index = start.getIndexInLine();
  416. mo << line.line.substring (index, len);
  417. }
  418. else if (i == endLine)
  419. {
  420. len = end.getIndexInLine();
  421. mo << line.line.substring (0, len);
  422. }
  423. else
  424. {
  425. mo << line.line;
  426. }
  427. }
  428. return mo.toUTF8();
  429. }
  430. int CodeDocument::getNumCharacters() const noexcept
  431. {
  432. if (const CodeDocumentLine* const lastLine = lines.getLast())
  433. return lastLine->lineStartInFile + lastLine->lineLength;
  434. return 0;
  435. }
  436. String CodeDocument::getLine (const int lineIndex) const noexcept
  437. {
  438. if (const CodeDocumentLine* const line = lines [lineIndex])
  439. return line->line;
  440. return String();
  441. }
  442. int CodeDocument::getMaximumLineLength() noexcept
  443. {
  444. if (maximumLineLength < 0)
  445. {
  446. maximumLineLength = 0;
  447. for (int i = lines.size(); --i >= 0;)
  448. maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength);
  449. }
  450. return maximumLineLength;
  451. }
  452. void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition)
  453. {
  454. deleteSection (startPosition.getPosition(), endPosition.getPosition());
  455. }
  456. void CodeDocument::deleteSection (const int start, const int end)
  457. {
  458. remove (start, end, true);
  459. }
  460. void CodeDocument::insertText (const Position& position, const String& text)
  461. {
  462. insertText (position.getPosition(), text);
  463. }
  464. void CodeDocument::insertText (const int insertIndex, const String& text)
  465. {
  466. insert (text, insertIndex, true);
  467. }
  468. void CodeDocument::replaceSection (const int start, const int end, const String& newText)
  469. {
  470. insertText (end, newText);
  471. deleteSection (start, end);
  472. }
  473. void CodeDocument::applyChanges (const String& newContent)
  474. {
  475. const String corrected (StringArray::fromLines (newContent)
  476. .joinIntoString (newLineChars));
  477. TextDiff diff (getAllContent(), corrected);
  478. for (int i = 0; i < diff.changes.size(); ++i)
  479. {
  480. const TextDiff::Change& c = diff.changes.getReference(i);
  481. if (c.isDeletion())
  482. remove (c.start, c.start + c.length, true);
  483. else
  484. insert (c.insertedText, c.start, true);
  485. }
  486. }
  487. void CodeDocument::replaceAllContent (const String& newContent)
  488. {
  489. remove (0, getNumCharacters(), true);
  490. insert (newContent, 0, true);
  491. }
  492. bool CodeDocument::loadFromStream (InputStream& stream)
  493. {
  494. remove (0, getNumCharacters(), false);
  495. insert (stream.readEntireStreamAsString(), 0, false);
  496. setSavePoint();
  497. clearUndoHistory();
  498. return true;
  499. }
  500. bool CodeDocument::writeToStream (OutputStream& stream)
  501. {
  502. for (int i = 0; i < lines.size(); ++i)
  503. {
  504. String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string.
  505. const char* utf8 = temp.toUTF8();
  506. if (! stream.write (utf8, strlen (utf8)))
  507. return false;
  508. }
  509. return true;
  510. }
  511. void CodeDocument::setNewLineCharacters (const String& newChars) noexcept
  512. {
  513. jassert (newChars == "\r\n" || newChars == "\n" || newChars == "\r");
  514. newLineChars = newChars;
  515. }
  516. void CodeDocument::newTransaction()
  517. {
  518. undoManager.beginNewTransaction (String());
  519. }
  520. void CodeDocument::undo()
  521. {
  522. newTransaction();
  523. undoManager.undo();
  524. }
  525. void CodeDocument::redo()
  526. {
  527. undoManager.redo();
  528. }
  529. void CodeDocument::clearUndoHistory()
  530. {
  531. undoManager.clearUndoHistory();
  532. }
  533. void CodeDocument::setSavePoint() noexcept
  534. {
  535. indexOfSavedState = currentActionIndex;
  536. }
  537. bool CodeDocument::hasChangedSinceSavePoint() const noexcept
  538. {
  539. return currentActionIndex != indexOfSavedState;
  540. }
  541. //==============================================================================
  542. namespace CodeDocumentHelpers
  543. {
  544. static int getCharacterType (const juce_wchar character) noexcept
  545. {
  546. return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
  547. ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
  548. }
  549. static bool isTokenCharacter (const juce_wchar c) noexcept
  550. {
  551. return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_';
  552. }
  553. }
  554. CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept
  555. {
  556. Position p (position);
  557. const int maxDistance = 256;
  558. int i = 0;
  559. while (i < maxDistance
  560. && CharacterFunctions::isWhitespace (p.getCharacter())
  561. && (i == 0 || (p.getCharacter() != '\n'
  562. && p.getCharacter() != '\r')))
  563. {
  564. ++i;
  565. p.moveBy (1);
  566. }
  567. if (i == 0)
  568. {
  569. const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter());
  570. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter()))
  571. {
  572. ++i;
  573. p.moveBy (1);
  574. }
  575. while (i < maxDistance
  576. && CharacterFunctions::isWhitespace (p.getCharacter())
  577. && (i == 0 || (p.getCharacter() != '\n'
  578. && p.getCharacter() != '\r')))
  579. {
  580. ++i;
  581. p.moveBy (1);
  582. }
  583. }
  584. return p;
  585. }
  586. CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept
  587. {
  588. Position p (position);
  589. const int maxDistance = 256;
  590. int i = 0;
  591. bool stoppedAtLineStart = false;
  592. while (i < maxDistance)
  593. {
  594. const juce_wchar c = p.movedBy (-1).getCharacter();
  595. if (c == '\r' || c == '\n')
  596. {
  597. stoppedAtLineStart = true;
  598. if (i > 0)
  599. break;
  600. }
  601. if (! CharacterFunctions::isWhitespace (c))
  602. break;
  603. p.moveBy (-1);
  604. ++i;
  605. }
  606. if (i < maxDistance && ! stoppedAtLineStart)
  607. {
  608. const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter());
  609. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()))
  610. {
  611. p.moveBy (-1);
  612. ++i;
  613. }
  614. }
  615. return p;
  616. }
  617. void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept
  618. {
  619. end = pos;
  620. while (CodeDocumentHelpers::isTokenCharacter (end.getCharacter()))
  621. end.moveBy (1);
  622. start = end;
  623. while (start.getIndexInLine() > 0
  624. && CodeDocumentHelpers::isTokenCharacter (start.movedBy (-1).getCharacter()))
  625. start.moveBy (-1);
  626. }
  627. void CodeDocument::findLineContaining (const Position& pos, Position& s, Position& e) const noexcept
  628. {
  629. s.setLineAndIndex (pos.getLineNumber(), 0);
  630. e.setLineAndIndex (pos.getLineNumber() + 1, 0);
  631. }
  632. void CodeDocument::checkLastLineStatus()
  633. {
  634. while (lines.size() > 0
  635. && lines.getLast()->lineLength == 0
  636. && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
  637. {
  638. // remove any empty lines at the end if the preceding line doesn't end in a newline.
  639. lines.removeLast();
  640. }
  641. const CodeDocumentLine* const lastLine = lines.getLast();
  642. if (lastLine != nullptr && lastLine->endsWithLineBreak())
  643. {
  644. // check that there's an empty line at the end if the preceding one ends in a newline..
  645. lines.add (new CodeDocumentLine (StringRef(), StringRef(), 0, 0,
  646. lastLine->lineStartInFile + lastLine->lineLength));
  647. }
  648. }
  649. //==============================================================================
  650. void CodeDocument::addListener (CodeDocument::Listener* const l) noexcept { listeners.add (l); }
  651. void CodeDocument::removeListener (CodeDocument::Listener* const l) noexcept { listeners.remove (l); }
  652. //==============================================================================
  653. class CodeDocumentInsertAction : public UndoableAction
  654. {
  655. public:
  656. CodeDocumentInsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
  657. : owner (doc), text (t), insertPos (pos)
  658. {
  659. }
  660. bool perform() override
  661. {
  662. owner.currentActionIndex++;
  663. owner.insert (text, insertPos, false);
  664. return true;
  665. }
  666. bool undo() override
  667. {
  668. owner.currentActionIndex--;
  669. owner.remove (insertPos, insertPos + text.length(), false);
  670. return true;
  671. }
  672. int getSizeInUnits() override { return text.length() + 32; }
  673. private:
  674. CodeDocument& owner;
  675. const String text;
  676. const int insertPos;
  677. JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction)
  678. };
  679. void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
  680. {
  681. if (text.isNotEmpty())
  682. {
  683. if (undoable)
  684. {
  685. undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos));
  686. }
  687. else
  688. {
  689. Position pos (*this, insertPos);
  690. const int firstAffectedLine = pos.getLineNumber();
  691. CodeDocumentLine* const firstLine = lines [firstAffectedLine];
  692. String textInsideOriginalLine (text);
  693. if (firstLine != nullptr)
  694. {
  695. const int index = pos.getIndexInLine();
  696. textInsideOriginalLine = firstLine->line.substring (0, index)
  697. + textInsideOriginalLine
  698. + firstLine->line.substring (index);
  699. }
  700. maximumLineLength = -1;
  701. Array<CodeDocumentLine*> newLines;
  702. CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
  703. jassert (newLines.size() > 0);
  704. CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0);
  705. newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
  706. lines.set (firstAffectedLine, newFirstLine);
  707. if (newLines.size() > 1)
  708. lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);
  709. int lineStart = newFirstLine->lineStartInFile;
  710. for (int i = firstAffectedLine; i < lines.size(); ++i)
  711. {
  712. CodeDocumentLine& l = *lines.getUnchecked (i);
  713. l.lineStartInFile = lineStart;
  714. lineStart += l.lineLength;
  715. }
  716. checkLastLineStatus();
  717. const int newTextLength = text.length();
  718. for (int i = 0; i < positionsToMaintain.size(); ++i)
  719. {
  720. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  721. if (p.getPosition() >= insertPos)
  722. p.setPosition (p.getPosition() + newTextLength);
  723. }
  724. listeners.call (&CodeDocument::Listener::codeDocumentTextInserted, text, insertPos);
  725. }
  726. }
  727. }
  728. //==============================================================================
  729. class CodeDocumentDeleteAction : public UndoableAction
  730. {
  731. public:
  732. CodeDocumentDeleteAction (CodeDocument& doc, const int start, const int end) noexcept
  733. : owner (doc), startPos (start), endPos (end),
  734. removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
  735. CodeDocument::Position (doc, end)))
  736. {
  737. }
  738. bool perform() override
  739. {
  740. owner.currentActionIndex++;
  741. owner.remove (startPos, endPos, false);
  742. return true;
  743. }
  744. bool undo() override
  745. {
  746. owner.currentActionIndex--;
  747. owner.insert (removedText, startPos, false);
  748. return true;
  749. }
  750. int getSizeInUnits() override { return (endPos - startPos) + 32; }
  751. private:
  752. CodeDocument& owner;
  753. const int startPos, endPos;
  754. const String removedText;
  755. JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction)
  756. };
  757. void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
  758. {
  759. if (endPos <= startPos)
  760. return;
  761. if (undoable)
  762. {
  763. undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos));
  764. }
  765. else
  766. {
  767. Position startPosition (*this, startPos);
  768. Position endPosition (*this, endPos);
  769. maximumLineLength = -1;
  770. const int firstAffectedLine = startPosition.getLineNumber();
  771. const int endLine = endPosition.getLineNumber();
  772. CodeDocumentLine& firstLine = *lines.getUnchecked (firstAffectedLine);
  773. if (firstAffectedLine == endLine)
  774. {
  775. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  776. + firstLine.line.substring (endPosition.getIndexInLine());
  777. firstLine.updateLength();
  778. }
  779. else
  780. {
  781. CodeDocumentLine& lastLine = *lines.getUnchecked (endLine);
  782. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  783. + lastLine.line.substring (endPosition.getIndexInLine());
  784. firstLine.updateLength();
  785. int numLinesToRemove = endLine - firstAffectedLine;
  786. lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
  787. }
  788. for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
  789. {
  790. CodeDocumentLine& l = *lines.getUnchecked (i);
  791. const CodeDocumentLine& previousLine = *lines.getUnchecked (i - 1);
  792. l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
  793. }
  794. checkLastLineStatus();
  795. const int totalChars = getNumCharacters();
  796. for (int i = 0; i < positionsToMaintain.size(); ++i)
  797. {
  798. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  799. if (p.getPosition() > startPosition.getPosition())
  800. p.setPosition (jmax (startPos, p.getPosition() + startPos - endPos));
  801. if (p.getPosition() > totalChars)
  802. p.setPosition (totalChars);
  803. }
  804. listeners.call (&CodeDocument::Listener::codeDocumentTextDeleted, startPos, endPos);
  805. }
  806. }