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.

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