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.

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