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.

993 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class CodeDocumentLine
  18. {
  19. public:
  20. CodeDocumentLine (const String::CharPointerType startOfLine,
  21. const String::CharPointerType endOfLine,
  22. const int lineLen,
  23. const int numNewLineChars,
  24. const int startInFile)
  25. : line (startOfLine, endOfLine),
  26. lineStartInFile (startInFile),
  27. lineLength (lineLen),
  28. lineLengthWithoutNewLines (lineLen - numNewLineChars)
  29. {
  30. }
  31. static void createLines (Array<CodeDocumentLine*>& newLines, StringRef text)
  32. {
  33. String::CharPointerType t (text.text);
  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, t, lineLength,
  71. numNewLineChars, startOfLineInFile));
  72. }
  73. jassert (charNumInFile == text.length());
  74. }
  75. bool endsWithLineBreak() const noexcept
  76. {
  77. return lineLengthWithoutNewLines != lineLength;
  78. }
  79. void updateLength() noexcept
  80. {
  81. lineLength = 0;
  82. lineLengthWithoutNewLines = 0;
  83. String::CharPointerType t (line.getCharPointer());
  84. for (;;)
  85. {
  86. const juce_wchar c = t.getAndAdvance();
  87. if (c == 0)
  88. break;
  89. ++lineLength;
  90. if (c != '\n' && c != '\r')
  91. lineLengthWithoutNewLines = lineLength;
  92. }
  93. }
  94. String line;
  95. int lineStartInFile, lineLength, lineLengthWithoutNewLines;
  96. };
  97. //==============================================================================
  98. CodeDocument::Iterator::Iterator (const CodeDocument& doc) noexcept
  99. : document (&doc),
  100. charPointer (nullptr),
  101. line (0),
  102. position (0)
  103. {
  104. }
  105. CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) noexcept
  106. : document (other.document),
  107. charPointer (other.charPointer),
  108. line (other.line),
  109. position (other.position)
  110. {
  111. }
  112. CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) noexcept
  113. {
  114. document = other.document;
  115. charPointer = other.charPointer;
  116. line = other.line;
  117. position = other.position;
  118. return *this;
  119. }
  120. CodeDocument::Iterator::~Iterator() noexcept
  121. {
  122. }
  123. juce_wchar CodeDocument::Iterator::nextChar() noexcept
  124. {
  125. for (;;)
  126. {
  127. if (charPointer.getAddress() == nullptr)
  128. {
  129. if (const CodeDocumentLine* const l = document->lines[line])
  130. charPointer = l->line.getCharPointer();
  131. else
  132. return 0;
  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. if (const CodeDocumentLine* const l = document->lines[line])
  169. charPointer = l->line.getCharPointer();
  170. else
  171. return 0;
  172. }
  173. const juce_wchar c = *charPointer;
  174. if (c != 0)
  175. return c;
  176. if (const CodeDocumentLine* const l = document->lines [line + 1])
  177. return l->line[0];
  178. return 0;
  179. }
  180. void CodeDocument::Iterator::skipWhitespace() noexcept
  181. {
  182. while (CharacterFunctions::isWhitespace (peekNextChar()))
  183. skip();
  184. }
  185. bool CodeDocument::Iterator::isEOF() const noexcept
  186. {
  187. return charPointer.getAddress() == nullptr && line >= document->lines.size();
  188. }
  189. //==============================================================================
  190. CodeDocument::Position::Position() noexcept
  191. : owner (nullptr), characterPos (0), line (0),
  192. indexInLine (0), positionMaintained (false)
  193. {
  194. }
  195. CodeDocument::Position::Position (const CodeDocument& ownerDocument,
  196. const int line_, const int indexInLine_) noexcept
  197. : owner (const_cast <CodeDocument*> (&ownerDocument)),
  198. characterPos (0), line (line_),
  199. indexInLine (indexInLine_), positionMaintained (false)
  200. {
  201. setLineAndIndex (line_, indexInLine_);
  202. }
  203. CodeDocument::Position::Position (const CodeDocument& ownerDocument, 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. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  263. indexInLine = l.lineLengthWithoutNewLines;
  264. characterPos = l.lineStartInFile + indexInLine;
  265. }
  266. else
  267. {
  268. line = jmax (0, newLineNum);
  269. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  270. if (l.lineLengthWithoutNewLines > 0)
  271. indexInLine = jlimit (0, l.lineLengthWithoutNewLines, newIndexInLine);
  272. else
  273. indexInLine = 0;
  274. characterPos = l.lineStartInFile + indexInLine;
  275. }
  276. }
  277. }
  278. void CodeDocument::Position::setPosition (const int newPosition)
  279. {
  280. jassert (owner != nullptr);
  281. line = 0;
  282. indexInLine = 0;
  283. characterPos = 0;
  284. if (newPosition > 0)
  285. {
  286. int lineStart = 0;
  287. int lineEnd = owner->lines.size();
  288. for (;;)
  289. {
  290. if (lineEnd - lineStart < 4)
  291. {
  292. for (int i = lineStart; i < lineEnd; ++i)
  293. {
  294. const CodeDocumentLine& l = *owner->lines.getUnchecked (i);
  295. const int index = newPosition - l.lineStartInFile;
  296. if (index >= 0 && (index < l.lineLength || i == lineEnd - 1))
  297. {
  298. line = i;
  299. indexInLine = jmin (l.lineLengthWithoutNewLines, index);
  300. characterPos = l.lineStartInFile + indexInLine;
  301. }
  302. }
  303. break;
  304. }
  305. else
  306. {
  307. const int midIndex = (lineStart + lineEnd + 1) / 2;
  308. if (newPosition >= owner->lines.getUnchecked (midIndex)->lineStartInFile)
  309. lineStart = midIndex;
  310. else
  311. lineEnd = midIndex;
  312. }
  313. }
  314. }
  315. }
  316. void CodeDocument::Position::moveBy (int characterDelta)
  317. {
  318. jassert (owner != nullptr);
  319. if (characterDelta == 1)
  320. {
  321. setPosition (getPosition());
  322. // If moving right, make sure we don't get stuck between the \r and \n characters..
  323. if (line < owner->lines.size())
  324. {
  325. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  326. if (indexInLine + characterDelta < l.lineLength
  327. && indexInLine + characterDelta >= l.lineLengthWithoutNewLines + 1)
  328. ++characterDelta;
  329. }
  330. }
  331. setPosition (characterPos + characterDelta);
  332. }
  333. CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const
  334. {
  335. CodeDocument::Position p (*this);
  336. p.moveBy (characterDelta);
  337. return p;
  338. }
  339. CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const
  340. {
  341. CodeDocument::Position p (*this);
  342. p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine());
  343. return p;
  344. }
  345. juce_wchar CodeDocument::Position::getCharacter() const
  346. {
  347. if (const CodeDocumentLine* const l = owner->lines [line])
  348. return l->line [getIndexInLine()];
  349. return 0;
  350. }
  351. String CodeDocument::Position::getLineText() const
  352. {
  353. if (const CodeDocumentLine* const l = owner->lines [line])
  354. return l->line;
  355. return String::empty;
  356. }
  357. void CodeDocument::Position::setPositionMaintained (const bool isMaintained)
  358. {
  359. if (isMaintained != positionMaintained)
  360. {
  361. positionMaintained = isMaintained;
  362. if (owner != nullptr)
  363. {
  364. if (isMaintained)
  365. {
  366. jassert (! owner->positionsToMaintain.contains (this));
  367. owner->positionsToMaintain.add (this);
  368. }
  369. else
  370. {
  371. // If this happens, you may have deleted the document while there are Position objects that are still using it...
  372. jassert (owner->positionsToMaintain.contains (this));
  373. owner->positionsToMaintain.removeFirstMatchingValue (this);
  374. }
  375. }
  376. }
  377. }
  378. //==============================================================================
  379. CodeDocument::CodeDocument()
  380. : undoManager (std::numeric_limits<int>::max(), 10000),
  381. currentActionIndex (0),
  382. indexOfSavedState (-1),
  383. maximumLineLength (-1),
  384. newLineChars ("\r\n")
  385. {
  386. }
  387. CodeDocument::~CodeDocument()
  388. {
  389. }
  390. String CodeDocument::getAllContent() const
  391. {
  392. return getTextBetween (Position (*this, 0),
  393. Position (*this, lines.size(), 0));
  394. }
  395. String CodeDocument::getTextBetween (const Position& start, const Position& end) const
  396. {
  397. if (end.getPosition() <= start.getPosition())
  398. return String::empty;
  399. const int startLine = start.getLineNumber();
  400. const int endLine = end.getLineNumber();
  401. if (startLine == endLine)
  402. {
  403. if (CodeDocumentLine* const line = lines [startLine])
  404. return line->line.substring (start.getIndexInLine(), end.getIndexInLine());
  405. return String::empty;
  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.toUTF8();
  430. }
  431. int CodeDocument::getNumCharacters() const noexcept
  432. {
  433. if (const CodeDocumentLine* const lastLine = lines.getLast())
  434. return lastLine->lineStartInFile + lastLine->lineLength;
  435. return 0;
  436. }
  437. String CodeDocument::getLine (const int lineIndex) const noexcept
  438. {
  439. if (const CodeDocumentLine* const line = lines [lineIndex])
  440. return line->line;
  441. return String::empty;
  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. deleteSection (startPosition.getPosition(), endPosition.getPosition());
  456. }
  457. void CodeDocument::deleteSection (const int start, const int end)
  458. {
  459. remove (start, end, true);
  460. }
  461. void CodeDocument::insertText (const Position& position, const String& text)
  462. {
  463. insertText (position.getPosition(), text);
  464. }
  465. void CodeDocument::insertText (const int insertIndex, const String& text)
  466. {
  467. insert (text, insertIndex, true);
  468. }
  469. void CodeDocument::replaceSection (const int start, const int end, const String& newText)
  470. {
  471. insertText (end, newText);
  472. deleteSection (start, end);
  473. }
  474. void CodeDocument::applyChanges (const String& newContent)
  475. {
  476. const String corrected (StringArray::fromLines (newContent)
  477. .joinIntoString (newLineChars));
  478. TextDiff diff (getAllContent(), corrected);
  479. for (int i = 0; i < diff.changes.size(); ++i)
  480. {
  481. const TextDiff::Change& c = diff.changes.getReference(i);
  482. if (c.isDeletion())
  483. remove (c.start, c.start + c.length, true);
  484. else
  485. insert (c.insertedText, c.start, true);
  486. }
  487. }
  488. void CodeDocument::replaceAllContent (const String& newContent)
  489. {
  490. remove (0, getNumCharacters(), true);
  491. insert (newContent, 0, true);
  492. }
  493. bool CodeDocument::loadFromStream (InputStream& stream)
  494. {
  495. remove (0, getNumCharacters(), false);
  496. insert (stream.readEntireStreamAsString(), 0, false);
  497. setSavePoint();
  498. clearUndoHistory();
  499. return true;
  500. }
  501. bool CodeDocument::writeToStream (OutputStream& stream)
  502. {
  503. for (int i = 0; i < lines.size(); ++i)
  504. {
  505. String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string.
  506. const char* utf8 = temp.toUTF8();
  507. if (! stream.write (utf8, strlen (utf8)))
  508. return false;
  509. }
  510. return true;
  511. }
  512. void CodeDocument::setNewLineCharacters (const String& newChars) noexcept
  513. {
  514. jassert (newChars == "\r\n" || newChars == "\n" || newChars == "\r");
  515. newLineChars = newChars;
  516. }
  517. void CodeDocument::newTransaction()
  518. {
  519. undoManager.beginNewTransaction (String::empty);
  520. }
  521. void CodeDocument::undo()
  522. {
  523. newTransaction();
  524. undoManager.undo();
  525. }
  526. void CodeDocument::redo()
  527. {
  528. undoManager.redo();
  529. }
  530. void CodeDocument::clearUndoHistory()
  531. {
  532. undoManager.clearUndoHistory();
  533. }
  534. void CodeDocument::setSavePoint() noexcept
  535. {
  536. indexOfSavedState = currentActionIndex;
  537. }
  538. bool CodeDocument::hasChangedSinceSavePoint() const noexcept
  539. {
  540. return currentActionIndex != indexOfSavedState;
  541. }
  542. //==============================================================================
  543. namespace CodeDocumentHelpers
  544. {
  545. static int getCharacterType (const juce_wchar character) noexcept
  546. {
  547. return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
  548. ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
  549. }
  550. static bool isTokenCharacter (const juce_wchar c) noexcept
  551. {
  552. return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_';
  553. }
  554. }
  555. CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept
  556. {
  557. Position p (position);
  558. const int maxDistance = 256;
  559. int i = 0;
  560. while (i < maxDistance
  561. && CharacterFunctions::isWhitespace (p.getCharacter())
  562. && (i == 0 || (p.getCharacter() != '\n'
  563. && p.getCharacter() != '\r')))
  564. {
  565. ++i;
  566. p.moveBy (1);
  567. }
  568. if (i == 0)
  569. {
  570. const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter());
  571. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter()))
  572. {
  573. ++i;
  574. p.moveBy (1);
  575. }
  576. while (i < maxDistance
  577. && CharacterFunctions::isWhitespace (p.getCharacter())
  578. && (i == 0 || (p.getCharacter() != '\n'
  579. && p.getCharacter() != '\r')))
  580. {
  581. ++i;
  582. p.moveBy (1);
  583. }
  584. }
  585. return p;
  586. }
  587. CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept
  588. {
  589. Position p (position);
  590. const int maxDistance = 256;
  591. int i = 0;
  592. bool stoppedAtLineStart = false;
  593. while (i < maxDistance)
  594. {
  595. const juce_wchar c = p.movedBy (-1).getCharacter();
  596. if (c == '\r' || c == '\n')
  597. {
  598. stoppedAtLineStart = true;
  599. if (i > 0)
  600. break;
  601. }
  602. if (! CharacterFunctions::isWhitespace (c))
  603. break;
  604. p.moveBy (-1);
  605. ++i;
  606. }
  607. if (i < maxDistance && ! stoppedAtLineStart)
  608. {
  609. const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter());
  610. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()))
  611. {
  612. p.moveBy (-1);
  613. ++i;
  614. }
  615. }
  616. return p;
  617. }
  618. void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept
  619. {
  620. end = pos;
  621. while (CodeDocumentHelpers::isTokenCharacter (end.getCharacter()))
  622. end.moveBy (1);
  623. start = end;
  624. while (start.getIndexInLine() > 0
  625. && CodeDocumentHelpers::isTokenCharacter (start.movedBy (-1).getCharacter()))
  626. start.moveBy (-1);
  627. }
  628. void CodeDocument::findLineContaining (const Position& pos, Position& s, Position& e) const noexcept
  629. {
  630. s.setLineAndIndex (pos.getLineNumber(), 0);
  631. e.setLineAndIndex (pos.getLineNumber() + 1, 0);
  632. }
  633. void CodeDocument::checkLastLineStatus()
  634. {
  635. while (lines.size() > 0
  636. && lines.getLast()->lineLength == 0
  637. && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
  638. {
  639. // remove any empty lines at the end if the preceding line doesn't end in a newline.
  640. lines.removeLast();
  641. }
  642. const CodeDocumentLine* const lastLine = lines.getLast();
  643. if (lastLine != nullptr && lastLine->endsWithLineBreak())
  644. {
  645. // check that there's an empty line at the end if the preceding one ends in a newline..
  646. lines.add (new CodeDocumentLine (StringRef(), StringRef(), 0, 0,
  647. lastLine->lineStartInFile + lastLine->lineLength));
  648. }
  649. }
  650. //==============================================================================
  651. void CodeDocument::addListener (CodeDocument::Listener* const l) noexcept { listeners.add (l); }
  652. void CodeDocument::removeListener (CodeDocument::Listener* const l) noexcept { listeners.remove (l); }
  653. //==============================================================================
  654. class CodeDocumentInsertAction : public UndoableAction
  655. {
  656. public:
  657. CodeDocumentInsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
  658. : owner (doc), text (t), insertPos (pos)
  659. {
  660. }
  661. bool perform()
  662. {
  663. owner.currentActionIndex++;
  664. owner.insert (text, insertPos, false);
  665. return true;
  666. }
  667. bool undo()
  668. {
  669. owner.currentActionIndex--;
  670. owner.remove (insertPos, insertPos + text.length(), false);
  671. return true;
  672. }
  673. int getSizeInUnits() { return text.length() + 32; }
  674. private:
  675. CodeDocument& owner;
  676. const String text;
  677. const int insertPos;
  678. JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction)
  679. };
  680. void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
  681. {
  682. if (text.isNotEmpty())
  683. {
  684. if (undoable)
  685. {
  686. undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos));
  687. }
  688. else
  689. {
  690. Position pos (*this, insertPos);
  691. const int firstAffectedLine = pos.getLineNumber();
  692. CodeDocumentLine* const firstLine = lines [firstAffectedLine];
  693. String textInsideOriginalLine (text);
  694. if (firstLine != nullptr)
  695. {
  696. const int index = pos.getIndexInLine();
  697. textInsideOriginalLine = firstLine->line.substring (0, index)
  698. + textInsideOriginalLine
  699. + firstLine->line.substring (index);
  700. }
  701. maximumLineLength = -1;
  702. Array <CodeDocumentLine*> newLines;
  703. CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
  704. jassert (newLines.size() > 0);
  705. CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0);
  706. newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
  707. lines.set (firstAffectedLine, newFirstLine);
  708. if (newLines.size() > 1)
  709. lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);
  710. int lineStart = newFirstLine->lineStartInFile;
  711. for (int i = firstAffectedLine; i < lines.size(); ++i)
  712. {
  713. CodeDocumentLine& l = *lines.getUnchecked (i);
  714. l.lineStartInFile = lineStart;
  715. lineStart += l.lineLength;
  716. }
  717. checkLastLineStatus();
  718. const int newTextLength = text.length();
  719. for (int i = 0; i < positionsToMaintain.size(); ++i)
  720. {
  721. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  722. if (p.getPosition() >= insertPos)
  723. p.setPosition (p.getPosition() + newTextLength);
  724. }
  725. listeners.call (&CodeDocument::Listener::codeDocumentTextInserted, text, insertPos);
  726. }
  727. }
  728. }
  729. //==============================================================================
  730. class CodeDocumentDeleteAction : public UndoableAction
  731. {
  732. public:
  733. CodeDocumentDeleteAction (CodeDocument& doc, const int start, const int end) noexcept
  734. : owner (doc), startPos (start), endPos (end),
  735. removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
  736. CodeDocument::Position (doc, end)))
  737. {
  738. }
  739. bool perform()
  740. {
  741. owner.currentActionIndex++;
  742. owner.remove (startPos, endPos, false);
  743. return true;
  744. }
  745. bool undo()
  746. {
  747. owner.currentActionIndex--;
  748. owner.insert (removedText, startPos, false);
  749. return true;
  750. }
  751. int getSizeInUnits() { return (endPos - startPos) + 32; }
  752. private:
  753. CodeDocument& owner;
  754. const int startPos, endPos;
  755. const String removedText;
  756. JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction)
  757. };
  758. void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
  759. {
  760. if (endPos <= startPos)
  761. return;
  762. if (undoable)
  763. {
  764. undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos));
  765. }
  766. else
  767. {
  768. Position startPosition (*this, startPos);
  769. Position endPosition (*this, endPos);
  770. maximumLineLength = -1;
  771. const int firstAffectedLine = startPosition.getLineNumber();
  772. const int endLine = endPosition.getLineNumber();
  773. CodeDocumentLine& firstLine = *lines.getUnchecked (firstAffectedLine);
  774. if (firstAffectedLine == endLine)
  775. {
  776. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  777. + firstLine.line.substring (endPosition.getIndexInLine());
  778. firstLine.updateLength();
  779. }
  780. else
  781. {
  782. CodeDocumentLine& lastLine = *lines.getUnchecked (endLine);
  783. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  784. + lastLine.line.substring (endPosition.getIndexInLine());
  785. firstLine.updateLength();
  786. int numLinesToRemove = endLine - firstAffectedLine;
  787. lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
  788. }
  789. for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
  790. {
  791. CodeDocumentLine& l = *lines.getUnchecked (i);
  792. const CodeDocumentLine& previousLine = *lines.getUnchecked (i - 1);
  793. l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
  794. }
  795. checkLastLineStatus();
  796. const int totalChars = getNumCharacters();
  797. for (int i = 0; i < positionsToMaintain.size(); ++i)
  798. {
  799. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  800. if (p.getPosition() > startPosition.getPosition())
  801. p.setPosition (jmax (startPos, p.getPosition() + startPos - endPos));
  802. if (p.getPosition() > totalChars)
  803. p.setPosition (totalChars);
  804. }
  805. listeners.call (&CodeDocument::Listener::codeDocumentTextDeleted, startPos, endPos);
  806. }
  807. }