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.

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