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.

982 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. class CodeDocumentLine
  19. {
  20. public:
  21. CodeDocumentLine (const String::CharPointerType& line_,
  22. const int lineLength_,
  23. const int numNewLineChars,
  24. const int lineStartInFile_)
  25. : line (line_, (size_t) lineLength_),
  26. lineStartInFile (lineStartInFile_),
  27. lineLength (lineLength_),
  28. lineLengthWithoutNewLines (lineLength_ - numNewLineChars)
  29. {
  30. }
  31. static void createLines (Array <CodeDocumentLine*>& newLines, const String& text)
  32. {
  33. String::CharPointerType t (text.getCharPointer());
  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, 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. String::CharPointerType t (line.getCharPointer());
  84. for (;;)
  85. {
  86. const juce_wchar c = t.getAndAdvance();
  87. if (c == 0)
  88. break;
  89. ++lineLength;
  90. if (c != '\n' && c != '\r')
  91. lineLengthWithoutNewLines = lineLength;
  92. }
  93. }
  94. String line;
  95. int lineStartInFile, lineLength, lineLengthWithoutNewLines;
  96. };
  97. //==============================================================================
  98. CodeDocument::Iterator::Iterator (const CodeDocument& doc) noexcept
  99. : document (&doc),
  100. charPointer (nullptr),
  101. line (0),
  102. position (0)
  103. {
  104. }
  105. CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) noexcept
  106. : document (other.document),
  107. charPointer (other.charPointer),
  108. line (other.line),
  109. position (other.position)
  110. {
  111. }
  112. CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) noexcept
  113. {
  114. document = other.document;
  115. charPointer = other.charPointer;
  116. line = other.line;
  117. position = other.position;
  118. return *this;
  119. }
  120. CodeDocument::Iterator::~Iterator() noexcept
  121. {
  122. }
  123. juce_wchar CodeDocument::Iterator::nextChar() noexcept
  124. {
  125. for (;;)
  126. {
  127. if (charPointer.getAddress() == nullptr)
  128. {
  129. const CodeDocumentLine* const l = document->lines[line];
  130. if (l == nullptr)
  131. return 0;
  132. charPointer = l->line.getCharPointer();
  133. }
  134. const juce_wchar result = charPointer.getAndAdvance();
  135. if (result == 0)
  136. {
  137. ++line;
  138. charPointer = nullptr;
  139. }
  140. else
  141. {
  142. ++position;
  143. return result;
  144. }
  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. const CodeDocumentLine* const l = document->lines[line];
  169. if (l == nullptr)
  170. return 0;
  171. charPointer = l->line.getCharPointer();
  172. }
  173. const juce_wchar c = *charPointer;
  174. if (c != 0)
  175. return c;
  176. const CodeDocumentLine* const l = document->lines [line + 1];
  177. return l == nullptr ? 0 : l->line[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 line_, const int indexInLine_) noexcept
  196. : owner (const_cast <CodeDocument*> (&ownerDocument)),
  197. characterPos (0), line (line_),
  198. indexInLine (indexInLine_), positionMaintained (false)
  199. {
  200. setLineAndIndex (line_, indexInLine_);
  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. const CodeDocumentLine* const l = owner->lines [line];
  347. return l == nullptr ? 0 : l->line [getIndexInLine()];
  348. }
  349. String CodeDocument::Position::getLineText() const
  350. {
  351. const CodeDocumentLine* const l = owner->lines [line];
  352. return l == nullptr ? String::empty : l->line;
  353. }
  354. void CodeDocument::Position::setPositionMaintained (const bool isMaintained)
  355. {
  356. if (isMaintained != positionMaintained)
  357. {
  358. positionMaintained = isMaintained;
  359. if (owner != nullptr)
  360. {
  361. if (isMaintained)
  362. {
  363. jassert (! owner->positionsToMaintain.contains (this));
  364. owner->positionsToMaintain.add (this);
  365. }
  366. else
  367. {
  368. // If this happens, you may have deleted the document while there are Position objects that are still using it...
  369. jassert (owner->positionsToMaintain.contains (this));
  370. owner->positionsToMaintain.removeFirstMatchingValue (this);
  371. }
  372. }
  373. }
  374. }
  375. //==============================================================================
  376. CodeDocument::CodeDocument()
  377. : undoManager (std::numeric_limits<int>::max(), 10000),
  378. currentActionIndex (0),
  379. indexOfSavedState (-1),
  380. maximumLineLength (-1),
  381. newLineChars ("\r\n")
  382. {
  383. }
  384. CodeDocument::~CodeDocument()
  385. {
  386. }
  387. String CodeDocument::getAllContent() const
  388. {
  389. return getTextBetween (Position (*this, 0),
  390. Position (*this, lines.size(), 0));
  391. }
  392. String CodeDocument::getTextBetween (const Position& start, const Position& end) const
  393. {
  394. if (end.getPosition() <= start.getPosition())
  395. return String::empty;
  396. const int startLine = start.getLineNumber();
  397. const int endLine = end.getLineNumber();
  398. if (startLine == endLine)
  399. {
  400. CodeDocumentLine* const line = lines [startLine];
  401. return (line == nullptr) ? String::empty : line->line.substring (start.getIndexInLine(), end.getIndexInLine());
  402. }
  403. MemoryOutputStream mo;
  404. mo.preallocate ((size_t) (end.getPosition() - start.getPosition() + 4));
  405. const int maxLine = jmin (lines.size() - 1, endLine);
  406. for (int i = jmax (0, startLine); i <= maxLine; ++i)
  407. {
  408. const CodeDocumentLine& line = *lines.getUnchecked(i);
  409. int len = line.lineLength;
  410. if (i == startLine)
  411. {
  412. const int index = start.getIndexInLine();
  413. mo << line.line.substring (index, len);
  414. }
  415. else if (i == endLine)
  416. {
  417. len = end.getIndexInLine();
  418. mo << line.line.substring (0, len);
  419. }
  420. else
  421. {
  422. mo << line.line;
  423. }
  424. }
  425. return mo.toString();
  426. }
  427. int CodeDocument::getNumCharacters() const noexcept
  428. {
  429. const CodeDocumentLine* const lastLine = lines.getLast();
  430. return (lastLine == nullptr) ? 0 : lastLine->lineStartInFile + lastLine->lineLength;
  431. }
  432. String CodeDocument::getLine (const int lineIndex) const noexcept
  433. {
  434. const CodeDocumentLine* const line = lines [lineIndex];
  435. return (line == nullptr) ? String::empty : line->line;
  436. }
  437. int CodeDocument::getMaximumLineLength() noexcept
  438. {
  439. if (maximumLineLength < 0)
  440. {
  441. maximumLineLength = 0;
  442. for (int i = lines.size(); --i >= 0;)
  443. maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength);
  444. }
  445. return maximumLineLength;
  446. }
  447. void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition)
  448. {
  449. deleteSection (startPosition.getPosition(), endPosition.getPosition());
  450. }
  451. void CodeDocument::deleteSection (const int start, const int end)
  452. {
  453. remove (start, end, true);
  454. }
  455. void CodeDocument::insertText (const Position& position, const String& text)
  456. {
  457. insertText (position.getPosition(), text);
  458. }
  459. void CodeDocument::insertText (const int insertIndex, const String& text)
  460. {
  461. insert (text, insertIndex, true);
  462. }
  463. void CodeDocument::replaceSection (const int start, const int end, const String& newText)
  464. {
  465. insertText (start, newText);
  466. const int newTextLen = newText.length();
  467. deleteSection (start + newTextLen, end + newTextLen);
  468. }
  469. void CodeDocument::applyChanges (const String& newContent)
  470. {
  471. TextDiff diff (getAllContent(), newContent);
  472. for (int i = 0; i < diff.changes.size(); ++i)
  473. {
  474. const TextDiff::Change& c = diff.changes.getReference(i);
  475. if (c.isDeletion())
  476. remove (c.start, c.start + c.length, true);
  477. else
  478. insert (c.insertedText, c.start, true);
  479. }
  480. }
  481. void CodeDocument::replaceAllContent (const String& newContent)
  482. {
  483. remove (0, getNumCharacters(), true);
  484. insert (newContent, 0, true);
  485. }
  486. bool CodeDocument::loadFromStream (InputStream& stream)
  487. {
  488. remove (0, getNumCharacters(), false);
  489. insert (stream.readEntireStreamAsString(), 0, false);
  490. setSavePoint();
  491. clearUndoHistory();
  492. return true;
  493. }
  494. bool CodeDocument::writeToStream (OutputStream& stream)
  495. {
  496. for (int i = 0; i < lines.size(); ++i)
  497. {
  498. String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string.
  499. const char* utf8 = temp.toUTF8();
  500. if (! stream.write (utf8, strlen (utf8)))
  501. return false;
  502. }
  503. return true;
  504. }
  505. void CodeDocument::setNewLineCharacters (const String& newLineChars_) noexcept
  506. {
  507. jassert (newLineChars_ == "\r\n" || newLineChars_ == "\n" || newLineChars_ == "\r");
  508. newLineChars = newLineChars_;
  509. }
  510. void CodeDocument::newTransaction()
  511. {
  512. undoManager.beginNewTransaction (String::empty);
  513. }
  514. void CodeDocument::undo()
  515. {
  516. newTransaction();
  517. undoManager.undo();
  518. }
  519. void CodeDocument::redo()
  520. {
  521. undoManager.redo();
  522. }
  523. void CodeDocument::clearUndoHistory()
  524. {
  525. undoManager.clearUndoHistory();
  526. }
  527. void CodeDocument::setSavePoint() noexcept
  528. {
  529. indexOfSavedState = currentActionIndex;
  530. }
  531. bool CodeDocument::hasChangedSinceSavePoint() const noexcept
  532. {
  533. return currentActionIndex != indexOfSavedState;
  534. }
  535. //==============================================================================
  536. namespace CodeDocumentHelpers
  537. {
  538. static int getCharacterType (const juce_wchar character) noexcept
  539. {
  540. return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
  541. ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
  542. }
  543. static bool isTokenCharacter (const juce_wchar c) noexcept
  544. {
  545. return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_';
  546. }
  547. }
  548. CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept
  549. {
  550. Position p (position);
  551. const int maxDistance = 256;
  552. int i = 0;
  553. while (i < maxDistance
  554. && CharacterFunctions::isWhitespace (p.getCharacter())
  555. && (i == 0 || (p.getCharacter() != '\n'
  556. && p.getCharacter() != '\r')))
  557. {
  558. ++i;
  559. p.moveBy (1);
  560. }
  561. if (i == 0)
  562. {
  563. const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter());
  564. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter()))
  565. {
  566. ++i;
  567. p.moveBy (1);
  568. }
  569. while (i < maxDistance
  570. && CharacterFunctions::isWhitespace (p.getCharacter())
  571. && (i == 0 || (p.getCharacter() != '\n'
  572. && p.getCharacter() != '\r')))
  573. {
  574. ++i;
  575. p.moveBy (1);
  576. }
  577. }
  578. return p;
  579. }
  580. CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept
  581. {
  582. Position p (position);
  583. const int maxDistance = 256;
  584. int i = 0;
  585. bool stoppedAtLineStart = false;
  586. while (i < maxDistance)
  587. {
  588. const juce_wchar c = p.movedBy (-1).getCharacter();
  589. if (c == '\r' || c == '\n')
  590. {
  591. stoppedAtLineStart = true;
  592. if (i > 0)
  593. break;
  594. }
  595. if (! CharacterFunctions::isWhitespace (c))
  596. break;
  597. p.moveBy (-1);
  598. ++i;
  599. }
  600. if (i < maxDistance && ! stoppedAtLineStart)
  601. {
  602. const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter());
  603. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()))
  604. {
  605. p.moveBy (-1);
  606. ++i;
  607. }
  608. }
  609. return p;
  610. }
  611. void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept
  612. {
  613. end = pos;
  614. while (CodeDocumentHelpers::isTokenCharacter (end.getCharacter()))
  615. end.moveBy (1);
  616. start = end;
  617. while (start.getIndexInLine() > 0
  618. && CodeDocumentHelpers::isTokenCharacter (start.movedBy (-1).getCharacter()))
  619. start.moveBy (-1);
  620. }
  621. void CodeDocument::findLineContaining (const Position& pos, Position& s, Position& e) const noexcept
  622. {
  623. s.setLineAndIndex (pos.getLineNumber(), 0);
  624. e.setLineAndIndex (pos.getLineNumber() + 1, 0);
  625. }
  626. void CodeDocument::checkLastLineStatus()
  627. {
  628. while (lines.size() > 0
  629. && lines.getLast()->lineLength == 0
  630. && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
  631. {
  632. // remove any empty lines at the end if the preceding line doesn't end in a newline.
  633. lines.removeLast();
  634. }
  635. const CodeDocumentLine* const lastLine = lines.getLast();
  636. if (lastLine != nullptr && lastLine->endsWithLineBreak())
  637. {
  638. // check that there's an empty line at the end if the preceding one ends in a newline..
  639. lines.add (new CodeDocumentLine (String::empty.getCharPointer(), 0, 0, lastLine->lineStartInFile + lastLine->lineLength));
  640. }
  641. }
  642. //==============================================================================
  643. void CodeDocument::addListener (CodeDocument::Listener* const l) noexcept { listeners.add (l); }
  644. void CodeDocument::removeListener (CodeDocument::Listener* const l) noexcept { listeners.remove (l); }
  645. //==============================================================================
  646. class CodeDocumentInsertAction : public UndoableAction
  647. {
  648. public:
  649. CodeDocumentInsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
  650. : owner (doc), text (t), insertPos (pos)
  651. {
  652. }
  653. bool perform()
  654. {
  655. owner.currentActionIndex++;
  656. owner.insert (text, insertPos, false);
  657. return true;
  658. }
  659. bool undo()
  660. {
  661. owner.currentActionIndex--;
  662. owner.remove (insertPos, insertPos + text.length(), false);
  663. return true;
  664. }
  665. int getSizeInUnits() { return text.length() + 32; }
  666. private:
  667. CodeDocument& owner;
  668. const String text;
  669. const int insertPos;
  670. JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction)
  671. };
  672. void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
  673. {
  674. if (text.isNotEmpty())
  675. {
  676. if (undoable)
  677. {
  678. undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos));
  679. }
  680. else
  681. {
  682. Position pos (*this, insertPos);
  683. const int firstAffectedLine = pos.getLineNumber();
  684. CodeDocumentLine* const firstLine = lines [firstAffectedLine];
  685. String textInsideOriginalLine (text);
  686. if (firstLine != nullptr)
  687. {
  688. const int index = pos.getIndexInLine();
  689. textInsideOriginalLine = firstLine->line.substring (0, index)
  690. + textInsideOriginalLine
  691. + firstLine->line.substring (index);
  692. }
  693. maximumLineLength = -1;
  694. Array <CodeDocumentLine*> newLines;
  695. CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
  696. jassert (newLines.size() > 0);
  697. CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0);
  698. newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
  699. lines.set (firstAffectedLine, newFirstLine);
  700. if (newLines.size() > 1)
  701. lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);
  702. int lineStart = newFirstLine->lineStartInFile;
  703. for (int i = firstAffectedLine; i < lines.size(); ++i)
  704. {
  705. CodeDocumentLine& l = *lines.getUnchecked (i);
  706. l.lineStartInFile = lineStart;
  707. lineStart += l.lineLength;
  708. }
  709. checkLastLineStatus();
  710. const int newTextLength = text.length();
  711. for (int i = 0; i < positionsToMaintain.size(); ++i)
  712. {
  713. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  714. if (p.getPosition() >= insertPos)
  715. p.setPosition (p.getPosition() + newTextLength);
  716. }
  717. listeners.call (&CodeDocument::Listener::codeDocumentTextInserted, text, insertPos);
  718. }
  719. }
  720. }
  721. //==============================================================================
  722. class CodeDocumentDeleteAction : public UndoableAction
  723. {
  724. public:
  725. CodeDocumentDeleteAction (CodeDocument& doc, const int start, const int end) noexcept
  726. : owner (doc), startPos (start), endPos (end),
  727. removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
  728. CodeDocument::Position (doc, end)))
  729. {
  730. }
  731. bool perform()
  732. {
  733. owner.currentActionIndex++;
  734. owner.remove (startPos, endPos, false);
  735. return true;
  736. }
  737. bool undo()
  738. {
  739. owner.currentActionIndex--;
  740. owner.insert (removedText, startPos, false);
  741. return true;
  742. }
  743. int getSizeInUnits() { return (endPos - startPos) + 32; }
  744. private:
  745. CodeDocument& owner;
  746. const int startPos, endPos;
  747. const String removedText;
  748. JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction)
  749. };
  750. void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
  751. {
  752. if (endPos <= startPos)
  753. return;
  754. if (undoable)
  755. {
  756. undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos));
  757. }
  758. else
  759. {
  760. Position startPosition (*this, startPos);
  761. Position endPosition (*this, endPos);
  762. maximumLineLength = -1;
  763. const int firstAffectedLine = startPosition.getLineNumber();
  764. const int endLine = endPosition.getLineNumber();
  765. CodeDocumentLine& firstLine = *lines.getUnchecked (firstAffectedLine);
  766. if (firstAffectedLine == endLine)
  767. {
  768. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  769. + firstLine.line.substring (endPosition.getIndexInLine());
  770. firstLine.updateLength();
  771. }
  772. else
  773. {
  774. CodeDocumentLine& lastLine = *lines.getUnchecked (endLine);
  775. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  776. + lastLine.line.substring (endPosition.getIndexInLine());
  777. firstLine.updateLength();
  778. int numLinesToRemove = endLine - firstAffectedLine;
  779. lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
  780. }
  781. for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
  782. {
  783. CodeDocumentLine& l = *lines.getUnchecked (i);
  784. const CodeDocumentLine& previousLine = *lines.getUnchecked (i - 1);
  785. l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
  786. }
  787. checkLastLineStatus();
  788. const int totalChars = getNumCharacters();
  789. for (int i = 0; i < positionsToMaintain.size(); ++i)
  790. {
  791. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  792. if (p.getPosition() > startPosition.getPosition())
  793. p.setPosition (jmax (startPos, p.getPosition() + startPos - endPos));
  794. if (p.getPosition() > totalChars)
  795. p.setPosition (totalChars);
  796. }
  797. listeners.call (&CodeDocument::Listener::codeDocumentTextDeleted, startPos, endPos);
  798. }
  799. }