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.

965 lines
27KB

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