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.

942 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 (const CodeDocument& document_) noexcept
  99. : document (&document_),
  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. remove (startPosition.getPosition(), endPosition.getPosition(), true);
  450. }
  451. void CodeDocument::insertText (const Position& position, const String& text)
  452. {
  453. insert (text, position.getPosition(), true);
  454. }
  455. void CodeDocument::replaceAllContent (const String& newContent)
  456. {
  457. remove (0, getNumCharacters(), true);
  458. insert (newContent, 0, true);
  459. }
  460. bool CodeDocument::loadFromStream (InputStream& stream)
  461. {
  462. remove (0, getNumCharacters(), false);
  463. insert (stream.readEntireStreamAsString(), 0, false);
  464. setSavePoint();
  465. clearUndoHistory();
  466. return true;
  467. }
  468. bool CodeDocument::writeToStream (OutputStream& stream)
  469. {
  470. for (int i = 0; i < lines.size(); ++i)
  471. {
  472. String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string.
  473. const char* utf8 = temp.toUTF8();
  474. if (! stream.write (utf8, (int) strlen (utf8)))
  475. return false;
  476. }
  477. return true;
  478. }
  479. void CodeDocument::setNewLineCharacters (const String& newLineChars_) noexcept
  480. {
  481. jassert (newLineChars_ == "\r\n" || newLineChars_ == "\n" || newLineChars_ == "\r");
  482. newLineChars = newLineChars_;
  483. }
  484. void CodeDocument::newTransaction()
  485. {
  486. undoManager.beginNewTransaction (String::empty);
  487. }
  488. void CodeDocument::undo()
  489. {
  490. newTransaction();
  491. undoManager.undo();
  492. }
  493. void CodeDocument::redo()
  494. {
  495. undoManager.redo();
  496. }
  497. void CodeDocument::clearUndoHistory()
  498. {
  499. undoManager.clearUndoHistory();
  500. }
  501. void CodeDocument::setSavePoint() noexcept
  502. {
  503. indexOfSavedState = currentActionIndex;
  504. }
  505. bool CodeDocument::hasChangedSinceSavePoint() const noexcept
  506. {
  507. return currentActionIndex != indexOfSavedState;
  508. }
  509. //==============================================================================
  510. namespace CodeDocumentHelpers
  511. {
  512. static int getCharacterType (const juce_wchar character) noexcept
  513. {
  514. return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
  515. ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
  516. }
  517. }
  518. const CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept
  519. {
  520. Position p (position);
  521. const int maxDistance = 256;
  522. int i = 0;
  523. while (i < maxDistance
  524. && CharacterFunctions::isWhitespace (p.getCharacter())
  525. && (i == 0 || (p.getCharacter() != '\n'
  526. && p.getCharacter() != '\r')))
  527. {
  528. ++i;
  529. p.moveBy (1);
  530. }
  531. if (i == 0)
  532. {
  533. const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter());
  534. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter()))
  535. {
  536. ++i;
  537. p.moveBy (1);
  538. }
  539. while (i < maxDistance
  540. && CharacterFunctions::isWhitespace (p.getCharacter())
  541. && (i == 0 || (p.getCharacter() != '\n'
  542. && p.getCharacter() != '\r')))
  543. {
  544. ++i;
  545. p.moveBy (1);
  546. }
  547. }
  548. return p;
  549. }
  550. const CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept
  551. {
  552. Position p (position);
  553. const int maxDistance = 256;
  554. int i = 0;
  555. bool stoppedAtLineStart = false;
  556. while (i < maxDistance)
  557. {
  558. const juce_wchar c = p.movedBy (-1).getCharacter();
  559. if (c == '\r' || c == '\n')
  560. {
  561. stoppedAtLineStart = true;
  562. if (i > 0)
  563. break;
  564. }
  565. if (! CharacterFunctions::isWhitespace (c))
  566. break;
  567. p.moveBy (-1);
  568. ++i;
  569. }
  570. if (i < maxDistance && ! stoppedAtLineStart)
  571. {
  572. const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter());
  573. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()))
  574. {
  575. p.moveBy (-1);
  576. ++i;
  577. }
  578. }
  579. return p;
  580. }
  581. void CodeDocument::checkLastLineStatus()
  582. {
  583. while (lines.size() > 0
  584. && lines.getLast()->lineLength == 0
  585. && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
  586. {
  587. // remove any empty lines at the end if the preceding line doesn't end in a newline.
  588. lines.removeLast();
  589. }
  590. const CodeDocumentLine* const lastLine = lines.getLast();
  591. if (lastLine != nullptr && lastLine->endsWithLineBreak())
  592. {
  593. // check that there's an empty line at the end if the preceding one ends in a newline..
  594. lines.add (new CodeDocumentLine (String::empty.getCharPointer(), 0, 0, lastLine->lineStartInFile + lastLine->lineLength));
  595. }
  596. }
  597. //==============================================================================
  598. void CodeDocument::addListener (CodeDocument::Listener* const l) noexcept { listeners.add (l); }
  599. void CodeDocument::removeListener (CodeDocument::Listener* const l) noexcept { listeners.remove (l); }
  600. void CodeDocument::sendListenerChangeMessage (const int startLine, const int endLine)
  601. {
  602. Position startPos (*this, startLine, 0);
  603. Position endPos (*this, endLine, 0);
  604. listeners.call (&CodeDocument::Listener::codeDocumentChanged, startPos, endPos);
  605. }
  606. //==============================================================================
  607. class CodeDocumentInsertAction : public UndoableAction
  608. {
  609. public:
  610. CodeDocumentInsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
  611. : owner (doc), text (t), insertPos (pos)
  612. {
  613. }
  614. bool perform()
  615. {
  616. owner.currentActionIndex++;
  617. owner.insert (text, insertPos, false);
  618. return true;
  619. }
  620. bool undo()
  621. {
  622. owner.currentActionIndex--;
  623. owner.remove (insertPos, insertPos + text.length(), false);
  624. return true;
  625. }
  626. int getSizeInUnits() { return text.length() + 32; }
  627. private:
  628. CodeDocument& owner;
  629. const String text;
  630. const int insertPos;
  631. JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction);
  632. };
  633. void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
  634. {
  635. if (text.isNotEmpty())
  636. {
  637. if (undoable)
  638. {
  639. undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos));
  640. }
  641. else
  642. {
  643. Position pos (*this, insertPos);
  644. const int firstAffectedLine = pos.getLineNumber();
  645. int lastAffectedLine = firstAffectedLine + 1;
  646. CodeDocumentLine* const firstLine = lines [firstAffectedLine];
  647. String textInsideOriginalLine (text);
  648. if (firstLine != nullptr)
  649. {
  650. const int index = pos.getIndexInLine();
  651. textInsideOriginalLine = firstLine->line.substring (0, index)
  652. + textInsideOriginalLine
  653. + firstLine->line.substring (index);
  654. }
  655. maximumLineLength = -1;
  656. Array <CodeDocumentLine*> newLines;
  657. CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
  658. jassert (newLines.size() > 0);
  659. CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0);
  660. newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
  661. lines.set (firstAffectedLine, newFirstLine);
  662. if (newLines.size() > 1)
  663. {
  664. lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);
  665. lastAffectedLine = lines.size();
  666. }
  667. int lineStart = newFirstLine->lineStartInFile;
  668. for (int i = firstAffectedLine; i < lines.size(); ++i)
  669. {
  670. CodeDocumentLine& l = *lines.getUnchecked (i);
  671. l.lineStartInFile = lineStart;
  672. lineStart += l.lineLength;
  673. }
  674. checkLastLineStatus();
  675. const int newTextLength = text.length();
  676. for (int i = 0; i < positionsToMaintain.size(); ++i)
  677. {
  678. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  679. if (p.getPosition() >= insertPos)
  680. p.setPosition (p.getPosition() + newTextLength);
  681. }
  682. sendListenerChangeMessage (firstAffectedLine, lastAffectedLine);
  683. }
  684. }
  685. }
  686. //==============================================================================
  687. class CodeDocumentDeleteAction : public UndoableAction
  688. {
  689. public:
  690. CodeDocumentDeleteAction (CodeDocument& doc, const int start, const int end) noexcept
  691. : owner (doc), startPos (start), endPos (end),
  692. removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
  693. CodeDocument::Position (doc, end)))
  694. {
  695. }
  696. bool perform()
  697. {
  698. owner.currentActionIndex++;
  699. owner.remove (startPos, endPos, false);
  700. return true;
  701. }
  702. bool undo()
  703. {
  704. owner.currentActionIndex--;
  705. owner.insert (removedText, startPos, false);
  706. return true;
  707. }
  708. int getSizeInUnits() { return (endPos - startPos) + 32; }
  709. private:
  710. CodeDocument& owner;
  711. const int startPos, endPos;
  712. const String removedText;
  713. JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction);
  714. };
  715. void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
  716. {
  717. if (endPos <= startPos)
  718. return;
  719. if (undoable)
  720. {
  721. undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos));
  722. }
  723. else
  724. {
  725. Position startPosition (*this, startPos);
  726. Position endPosition (*this, endPos);
  727. maximumLineLength = -1;
  728. const int firstAffectedLine = startPosition.getLineNumber();
  729. const int endLine = endPosition.getLineNumber();
  730. int lastAffectedLine = firstAffectedLine + 1;
  731. CodeDocumentLine& firstLine = *lines.getUnchecked (firstAffectedLine);
  732. if (firstAffectedLine == endLine)
  733. {
  734. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  735. + firstLine.line.substring (endPosition.getIndexInLine());
  736. firstLine.updateLength();
  737. }
  738. else
  739. {
  740. lastAffectedLine = lines.size();
  741. CodeDocumentLine& lastLine = *lines.getUnchecked (endLine);
  742. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  743. + lastLine.line.substring (endPosition.getIndexInLine());
  744. firstLine.updateLength();
  745. int numLinesToRemove = endLine - firstAffectedLine;
  746. lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
  747. }
  748. for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
  749. {
  750. CodeDocumentLine& l = *lines.getUnchecked (i);
  751. const CodeDocumentLine& previousLine = *lines.getUnchecked (i - 1);
  752. l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
  753. }
  754. checkLastLineStatus();
  755. const int totalChars = getNumCharacters();
  756. for (int i = 0; i < positionsToMaintain.size(); ++i)
  757. {
  758. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  759. if (p.getPosition() > startPosition.getPosition())
  760. p.setPosition (jmax (startPos, p.getPosition() + startPos - endPos));
  761. if (p.getPosition() > totalChars)
  762. p.setPosition (totalChars);
  763. }
  764. sendListenerChangeMessage (firstAffectedLine, lastAffectedLine);
  765. }
  766. }