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.

998 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. class CodeDocumentLine
  22. {
  23. public:
  24. CodeDocumentLine (const String::CharPointerType startOfLine,
  25. const String::CharPointerType endOfLine,
  26. const int lineLen,
  27. const int numNewLineChars,
  28. const int startInFile)
  29. : line (startOfLine, endOfLine),
  30. lineStartInFile (startInFile),
  31. lineLength (lineLen),
  32. lineLengthWithoutNewLines (lineLen - numNewLineChars)
  33. {
  34. }
  35. static void createLines (Array<CodeDocumentLine*>& newLines, StringRef text)
  36. {
  37. String::CharPointerType t (text.text);
  38. int charNumInFile = 0;
  39. bool finished = false;
  40. while (! (finished || t.isEmpty()))
  41. {
  42. String::CharPointerType startOfLine (t);
  43. int startOfLineInFile = charNumInFile;
  44. int lineLength = 0;
  45. int numNewLineChars = 0;
  46. for (;;)
  47. {
  48. const juce_wchar c = t.getAndAdvance();
  49. if (c == 0)
  50. {
  51. finished = true;
  52. break;
  53. }
  54. ++charNumInFile;
  55. ++lineLength;
  56. if (c == '\r')
  57. {
  58. ++numNewLineChars;
  59. if (*t == '\n')
  60. {
  61. ++t;
  62. ++charNumInFile;
  63. ++lineLength;
  64. ++numNewLineChars;
  65. }
  66. break;
  67. }
  68. if (c == '\n')
  69. {
  70. ++numNewLineChars;
  71. break;
  72. }
  73. }
  74. newLines.add (new CodeDocumentLine (startOfLine, t, lineLength,
  75. numNewLineChars, startOfLineInFile));
  76. }
  77. jassert (charNumInFile == text.length());
  78. }
  79. bool endsWithLineBreak() const noexcept
  80. {
  81. return lineLengthWithoutNewLines != lineLength;
  82. }
  83. void updateLength() noexcept
  84. {
  85. lineLength = 0;
  86. lineLengthWithoutNewLines = 0;
  87. for (String::CharPointerType t (line.getCharPointer());;)
  88. {
  89. const juce_wchar c = t.getAndAdvance();
  90. if (c == 0)
  91. break;
  92. ++lineLength;
  93. if (c != '\n' && c != '\r')
  94. lineLengthWithoutNewLines = lineLength;
  95. }
  96. }
  97. String line;
  98. int lineStartInFile, lineLength, lineLengthWithoutNewLines;
  99. };
  100. //==============================================================================
  101. CodeDocument::Iterator::Iterator (const CodeDocument& doc) noexcept
  102. : document (&doc),
  103. charPointer (nullptr),
  104. line (0),
  105. position (0)
  106. {
  107. }
  108. CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) noexcept
  109. : document (other.document),
  110. charPointer (other.charPointer),
  111. line (other.line),
  112. position (other.position)
  113. {
  114. }
  115. CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) noexcept
  116. {
  117. document = other.document;
  118. charPointer = other.charPointer;
  119. line = other.line;
  120. position = other.position;
  121. return *this;
  122. }
  123. CodeDocument::Iterator::~Iterator() noexcept
  124. {
  125. }
  126. juce_wchar CodeDocument::Iterator::nextChar() noexcept
  127. {
  128. for (;;)
  129. {
  130. if (charPointer.getAddress() == nullptr)
  131. {
  132. if (const CodeDocumentLine* const l = document->lines[line])
  133. charPointer = l->line.getCharPointer();
  134. else
  135. return 0;
  136. }
  137. if (const juce_wchar result = charPointer.getAndAdvance())
  138. {
  139. if (charPointer.isEmpty())
  140. {
  141. ++line;
  142. charPointer = nullptr;
  143. }
  144. ++position;
  145. return result;
  146. }
  147. ++line;
  148. charPointer = nullptr;
  149. }
  150. }
  151. void CodeDocument::Iterator::skip() noexcept
  152. {
  153. nextChar();
  154. }
  155. void CodeDocument::Iterator::skipToEndOfLine() noexcept
  156. {
  157. if (charPointer.getAddress() == nullptr)
  158. {
  159. const CodeDocumentLine* const l = document->lines[line];
  160. if (l == nullptr)
  161. return;
  162. charPointer = l->line.getCharPointer();
  163. }
  164. position += (int) charPointer.length();
  165. ++line;
  166. charPointer = nullptr;
  167. }
  168. juce_wchar CodeDocument::Iterator::peekNextChar() const noexcept
  169. {
  170. if (charPointer.getAddress() == nullptr)
  171. {
  172. if (const CodeDocumentLine* const l = document->lines[line])
  173. charPointer = l->line.getCharPointer();
  174. else
  175. return 0;
  176. }
  177. if (const juce_wchar c = *charPointer)
  178. return c;
  179. if (const CodeDocumentLine* const l = document->lines [line + 1])
  180. return l->line[0];
  181. return 0;
  182. }
  183. void CodeDocument::Iterator::skipWhitespace() noexcept
  184. {
  185. while (CharacterFunctions::isWhitespace (peekNextChar()))
  186. skip();
  187. }
  188. bool CodeDocument::Iterator::isEOF() const noexcept
  189. {
  190. return charPointer.getAddress() == nullptr && line >= document->lines.size();
  191. }
  192. //==============================================================================
  193. CodeDocument::Position::Position() noexcept
  194. : owner (nullptr), characterPos (0), line (0),
  195. indexInLine (0), positionMaintained (false)
  196. {
  197. }
  198. CodeDocument::Position::Position (const CodeDocument& ownerDocument,
  199. const int lineNum, const int index) noexcept
  200. : owner (const_cast<CodeDocument*> (&ownerDocument)),
  201. characterPos (0), line (lineNum),
  202. indexInLine (index), positionMaintained (false)
  203. {
  204. setLineAndIndex (lineNum, index);
  205. }
  206. CodeDocument::Position::Position (const CodeDocument& ownerDocument, const int characterPos_) noexcept
  207. : owner (const_cast<CodeDocument*> (&ownerDocument)),
  208. positionMaintained (false)
  209. {
  210. setPosition (characterPos_);
  211. }
  212. CodeDocument::Position::Position (const Position& other) noexcept
  213. : owner (other.owner), characterPos (other.characterPos), line (other.line),
  214. indexInLine (other.indexInLine), positionMaintained (false)
  215. {
  216. jassert (*this == other);
  217. }
  218. CodeDocument::Position::~Position()
  219. {
  220. setPositionMaintained (false);
  221. }
  222. CodeDocument::Position& CodeDocument::Position::operator= (const Position& other)
  223. {
  224. if (this != &other)
  225. {
  226. const bool wasPositionMaintained = positionMaintained;
  227. if (owner != other.owner)
  228. setPositionMaintained (false);
  229. owner = other.owner;
  230. line = other.line;
  231. indexInLine = other.indexInLine;
  232. characterPos = other.characterPos;
  233. setPositionMaintained (wasPositionMaintained);
  234. jassert (*this == other);
  235. }
  236. return *this;
  237. }
  238. bool CodeDocument::Position::operator== (const Position& other) const noexcept
  239. {
  240. jassert ((characterPos == other.characterPos)
  241. == (line == other.line && indexInLine == other.indexInLine));
  242. return characterPos == other.characterPos
  243. && line == other.line
  244. && indexInLine == other.indexInLine
  245. && owner == other.owner;
  246. }
  247. bool CodeDocument::Position::operator!= (const Position& other) const noexcept
  248. {
  249. return ! operator== (other);
  250. }
  251. void CodeDocument::Position::setLineAndIndex (const int newLineNum, const int newIndexInLine)
  252. {
  253. jassert (owner != nullptr);
  254. if (owner->lines.size() == 0)
  255. {
  256. line = 0;
  257. indexInLine = 0;
  258. characterPos = 0;
  259. }
  260. else
  261. {
  262. if (newLineNum >= owner->lines.size())
  263. {
  264. line = owner->lines.size() - 1;
  265. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  266. indexInLine = l.lineLengthWithoutNewLines;
  267. characterPos = l.lineStartInFile + indexInLine;
  268. }
  269. else
  270. {
  271. line = jmax (0, newLineNum);
  272. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  273. if (l.lineLengthWithoutNewLines > 0)
  274. indexInLine = jlimit (0, l.lineLengthWithoutNewLines, newIndexInLine);
  275. else
  276. indexInLine = 0;
  277. characterPos = l.lineStartInFile + indexInLine;
  278. }
  279. }
  280. }
  281. void CodeDocument::Position::setPosition (const int newPosition)
  282. {
  283. jassert (owner != nullptr);
  284. line = 0;
  285. indexInLine = 0;
  286. characterPos = 0;
  287. if (newPosition > 0)
  288. {
  289. int lineStart = 0;
  290. int lineEnd = owner->lines.size();
  291. for (;;)
  292. {
  293. if (lineEnd - lineStart < 4)
  294. {
  295. for (int i = lineStart; i < lineEnd; ++i)
  296. {
  297. const CodeDocumentLine& l = *owner->lines.getUnchecked (i);
  298. const int index = newPosition - l.lineStartInFile;
  299. if (index >= 0 && (index < l.lineLength || i == lineEnd - 1))
  300. {
  301. line = i;
  302. indexInLine = jmin (l.lineLengthWithoutNewLines, index);
  303. characterPos = l.lineStartInFile + indexInLine;
  304. }
  305. }
  306. break;
  307. }
  308. else
  309. {
  310. const int midIndex = (lineStart + lineEnd + 1) / 2;
  311. if (newPosition >= owner->lines.getUnchecked (midIndex)->lineStartInFile)
  312. lineStart = midIndex;
  313. else
  314. lineEnd = midIndex;
  315. }
  316. }
  317. }
  318. }
  319. void CodeDocument::Position::moveBy (int characterDelta)
  320. {
  321. jassert (owner != nullptr);
  322. if (characterDelta == 1)
  323. {
  324. setPosition (getPosition());
  325. // If moving right, make sure we don't get stuck between the \r and \n characters..
  326. if (line < owner->lines.size())
  327. {
  328. const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
  329. if (indexInLine + characterDelta < l.lineLength
  330. && indexInLine + characterDelta >= l.lineLengthWithoutNewLines + 1)
  331. ++characterDelta;
  332. }
  333. }
  334. setPosition (characterPos + characterDelta);
  335. }
  336. CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const
  337. {
  338. CodeDocument::Position p (*this);
  339. p.moveBy (characterDelta);
  340. return p;
  341. }
  342. CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const
  343. {
  344. CodeDocument::Position p (*this);
  345. p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine());
  346. return p;
  347. }
  348. juce_wchar CodeDocument::Position::getCharacter() const
  349. {
  350. if (auto* l = owner->lines [line])
  351. return l->line [getIndexInLine()];
  352. return 0;
  353. }
  354. String CodeDocument::Position::getLineText() const
  355. {
  356. if (auto* l = owner->lines [line])
  357. return l->line;
  358. return {};
  359. }
  360. void CodeDocument::Position::setPositionMaintained (const bool isMaintained)
  361. {
  362. if (isMaintained != positionMaintained)
  363. {
  364. positionMaintained = isMaintained;
  365. if (owner != nullptr)
  366. {
  367. if (isMaintained)
  368. {
  369. jassert (! owner->positionsToMaintain.contains (this));
  370. owner->positionsToMaintain.add (this);
  371. }
  372. else
  373. {
  374. // If this happens, you may have deleted the document while there are Position objects that are still using it...
  375. jassert (owner->positionsToMaintain.contains (this));
  376. owner->positionsToMaintain.removeFirstMatchingValue (this);
  377. }
  378. }
  379. }
  380. }
  381. //==============================================================================
  382. CodeDocument::CodeDocument()
  383. : undoManager (std::numeric_limits<int>::max(), 10000),
  384. currentActionIndex (0),
  385. indexOfSavedState (-1),
  386. maximumLineLength (-1),
  387. newLineChars ("\r\n")
  388. {
  389. }
  390. CodeDocument::~CodeDocument()
  391. {
  392. }
  393. String CodeDocument::getAllContent() const
  394. {
  395. return getTextBetween (Position (*this, 0),
  396. Position (*this, lines.size(), 0));
  397. }
  398. String CodeDocument::getTextBetween (const Position& start, const Position& end) const
  399. {
  400. if (end.getPosition() <= start.getPosition())
  401. return {};
  402. const int startLine = start.getLineNumber();
  403. const int endLine = end.getLineNumber();
  404. if (startLine == endLine)
  405. {
  406. if (CodeDocumentLine* const line = lines [startLine])
  407. return line->line.substring (start.getIndexInLine(), end.getIndexInLine());
  408. return {};
  409. }
  410. MemoryOutputStream mo;
  411. mo.preallocate ((size_t) (end.getPosition() - start.getPosition() + 4));
  412. const int maxLine = jmin (lines.size() - 1, endLine);
  413. for (int i = jmax (0, startLine); i <= maxLine; ++i)
  414. {
  415. const CodeDocumentLine& line = *lines.getUnchecked(i);
  416. int len = line.lineLength;
  417. if (i == startLine)
  418. {
  419. const int index = start.getIndexInLine();
  420. mo << line.line.substring (index, len);
  421. }
  422. else if (i == endLine)
  423. {
  424. len = end.getIndexInLine();
  425. mo << line.line.substring (0, len);
  426. }
  427. else
  428. {
  429. mo << line.line;
  430. }
  431. }
  432. return mo.toUTF8();
  433. }
  434. int CodeDocument::getNumCharacters() const noexcept
  435. {
  436. if (auto* lastLine = lines.getLast())
  437. return lastLine->lineStartInFile + lastLine->lineLength;
  438. return 0;
  439. }
  440. String CodeDocument::getLine (const int lineIndex) const noexcept
  441. {
  442. if (auto* line = lines [lineIndex])
  443. return line->line;
  444. return {};
  445. }
  446. int CodeDocument::getMaximumLineLength() noexcept
  447. {
  448. if (maximumLineLength < 0)
  449. {
  450. maximumLineLength = 0;
  451. for (int i = lines.size(); --i >= 0;)
  452. maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength);
  453. }
  454. return maximumLineLength;
  455. }
  456. void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition)
  457. {
  458. deleteSection (startPosition.getPosition(), endPosition.getPosition());
  459. }
  460. void CodeDocument::deleteSection (const int start, const int end)
  461. {
  462. remove (start, end, true);
  463. }
  464. void CodeDocument::insertText (const Position& position, const String& text)
  465. {
  466. insertText (position.getPosition(), text);
  467. }
  468. void CodeDocument::insertText (const int insertIndex, const String& text)
  469. {
  470. insert (text, insertIndex, true);
  471. }
  472. void CodeDocument::replaceSection (const int start, const int end, const String& newText)
  473. {
  474. insertText (end, newText);
  475. deleteSection (start, end);
  476. }
  477. void CodeDocument::applyChanges (const String& newContent)
  478. {
  479. const String corrected (StringArray::fromLines (newContent)
  480. .joinIntoString (newLineChars));
  481. TextDiff diff (getAllContent(), corrected);
  482. for (int i = 0; i < diff.changes.size(); ++i)
  483. {
  484. const TextDiff::Change& c = diff.changes.getReference(i);
  485. if (c.isDeletion())
  486. remove (c.start, c.start + c.length, true);
  487. else
  488. insert (c.insertedText, c.start, true);
  489. }
  490. }
  491. void CodeDocument::replaceAllContent (const String& newContent)
  492. {
  493. remove (0, getNumCharacters(), true);
  494. insert (newContent, 0, true);
  495. }
  496. bool CodeDocument::loadFromStream (InputStream& stream)
  497. {
  498. remove (0, getNumCharacters(), false);
  499. insert (stream.readEntireStreamAsString(), 0, false);
  500. setSavePoint();
  501. clearUndoHistory();
  502. return true;
  503. }
  504. bool CodeDocument::writeToStream (OutputStream& stream)
  505. {
  506. for (int i = 0; i < lines.size(); ++i)
  507. {
  508. String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string.
  509. const char* utf8 = temp.toUTF8();
  510. if (! stream.write (utf8, strlen (utf8)))
  511. return false;
  512. }
  513. return true;
  514. }
  515. void CodeDocument::setNewLineCharacters (const String& newChars) noexcept
  516. {
  517. jassert (newChars == "\r\n" || newChars == "\n" || newChars == "\r");
  518. newLineChars = newChars;
  519. }
  520. void CodeDocument::newTransaction()
  521. {
  522. undoManager.beginNewTransaction (String());
  523. }
  524. void CodeDocument::undo()
  525. {
  526. newTransaction();
  527. undoManager.undo();
  528. }
  529. void CodeDocument::redo()
  530. {
  531. undoManager.redo();
  532. }
  533. void CodeDocument::clearUndoHistory()
  534. {
  535. undoManager.clearUndoHistory();
  536. }
  537. void CodeDocument::setSavePoint() noexcept
  538. {
  539. indexOfSavedState = currentActionIndex;
  540. }
  541. bool CodeDocument::hasChangedSinceSavePoint() const noexcept
  542. {
  543. return currentActionIndex != indexOfSavedState;
  544. }
  545. //==============================================================================
  546. namespace CodeDocumentHelpers
  547. {
  548. static int getCharacterType (const juce_wchar character) noexcept
  549. {
  550. return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
  551. ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
  552. }
  553. static bool isTokenCharacter (const juce_wchar c) noexcept
  554. {
  555. return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_';
  556. }
  557. }
  558. CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept
  559. {
  560. Position p (position);
  561. const int maxDistance = 256;
  562. int i = 0;
  563. while (i < maxDistance
  564. && CharacterFunctions::isWhitespace (p.getCharacter())
  565. && (i == 0 || (p.getCharacter() != '\n'
  566. && p.getCharacter() != '\r')))
  567. {
  568. ++i;
  569. p.moveBy (1);
  570. }
  571. if (i == 0)
  572. {
  573. const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter());
  574. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter()))
  575. {
  576. ++i;
  577. p.moveBy (1);
  578. }
  579. while (i < maxDistance
  580. && CharacterFunctions::isWhitespace (p.getCharacter())
  581. && (i == 0 || (p.getCharacter() != '\n'
  582. && p.getCharacter() != '\r')))
  583. {
  584. ++i;
  585. p.moveBy (1);
  586. }
  587. }
  588. return p;
  589. }
  590. CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept
  591. {
  592. Position p (position);
  593. const int maxDistance = 256;
  594. int i = 0;
  595. bool stoppedAtLineStart = false;
  596. while (i < maxDistance)
  597. {
  598. const juce_wchar c = p.movedBy (-1).getCharacter();
  599. if (c == '\r' || c == '\n')
  600. {
  601. stoppedAtLineStart = true;
  602. if (i > 0)
  603. break;
  604. }
  605. if (! CharacterFunctions::isWhitespace (c))
  606. break;
  607. p.moveBy (-1);
  608. ++i;
  609. }
  610. if (i < maxDistance && ! stoppedAtLineStart)
  611. {
  612. const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter());
  613. while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()))
  614. {
  615. p.moveBy (-1);
  616. ++i;
  617. }
  618. }
  619. return p;
  620. }
  621. void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept
  622. {
  623. end = pos;
  624. while (CodeDocumentHelpers::isTokenCharacter (end.getCharacter()))
  625. end.moveBy (1);
  626. start = end;
  627. while (start.getIndexInLine() > 0
  628. && CodeDocumentHelpers::isTokenCharacter (start.movedBy (-1).getCharacter()))
  629. start.moveBy (-1);
  630. }
  631. void CodeDocument::findLineContaining (const Position& pos, Position& s, Position& e) const noexcept
  632. {
  633. s.setLineAndIndex (pos.getLineNumber(), 0);
  634. e.setLineAndIndex (pos.getLineNumber() + 1, 0);
  635. }
  636. void CodeDocument::checkLastLineStatus()
  637. {
  638. while (lines.size() > 0
  639. && lines.getLast()->lineLength == 0
  640. && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
  641. {
  642. // remove any empty lines at the end if the preceding line doesn't end in a newline.
  643. lines.removeLast();
  644. }
  645. const CodeDocumentLine* const lastLine = lines.getLast();
  646. if (lastLine != nullptr && lastLine->endsWithLineBreak())
  647. {
  648. // check that there's an empty line at the end if the preceding one ends in a newline..
  649. lines.add (new CodeDocumentLine (StringRef(), StringRef(), 0, 0,
  650. lastLine->lineStartInFile + lastLine->lineLength));
  651. }
  652. }
  653. //==============================================================================
  654. void CodeDocument::addListener (CodeDocument::Listener* const l) noexcept { listeners.add (l); }
  655. void CodeDocument::removeListener (CodeDocument::Listener* const l) noexcept { listeners.remove (l); }
  656. //==============================================================================
  657. class CodeDocumentInsertAction : public UndoableAction
  658. {
  659. public:
  660. CodeDocumentInsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
  661. : owner (doc), text (t), insertPos (pos)
  662. {
  663. }
  664. bool perform() override
  665. {
  666. owner.currentActionIndex++;
  667. owner.insert (text, insertPos, false);
  668. return true;
  669. }
  670. bool undo() override
  671. {
  672. owner.currentActionIndex--;
  673. owner.remove (insertPos, insertPos + text.length(), false);
  674. return true;
  675. }
  676. int getSizeInUnits() override { return text.length() + 32; }
  677. private:
  678. CodeDocument& owner;
  679. const String text;
  680. const int insertPos;
  681. JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction)
  682. };
  683. void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
  684. {
  685. if (text.isNotEmpty())
  686. {
  687. if (undoable)
  688. {
  689. undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos));
  690. }
  691. else
  692. {
  693. Position pos (*this, insertPos);
  694. const int firstAffectedLine = pos.getLineNumber();
  695. CodeDocumentLine* const firstLine = lines [firstAffectedLine];
  696. String textInsideOriginalLine (text);
  697. if (firstLine != nullptr)
  698. {
  699. const int index = pos.getIndexInLine();
  700. textInsideOriginalLine = firstLine->line.substring (0, index)
  701. + textInsideOriginalLine
  702. + firstLine->line.substring (index);
  703. }
  704. maximumLineLength = -1;
  705. Array<CodeDocumentLine*> newLines;
  706. CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
  707. jassert (newLines.size() > 0);
  708. CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0);
  709. newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
  710. lines.set (firstAffectedLine, newFirstLine);
  711. if (newLines.size() > 1)
  712. lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);
  713. int lineStart = newFirstLine->lineStartInFile;
  714. for (int i = firstAffectedLine; i < lines.size(); ++i)
  715. {
  716. CodeDocumentLine& l = *lines.getUnchecked (i);
  717. l.lineStartInFile = lineStart;
  718. lineStart += l.lineLength;
  719. }
  720. checkLastLineStatus();
  721. const int newTextLength = text.length();
  722. for (int i = 0; i < positionsToMaintain.size(); ++i)
  723. {
  724. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  725. if (p.getPosition() >= insertPos)
  726. p.setPosition (p.getPosition() + newTextLength);
  727. }
  728. listeners.call (&CodeDocument::Listener::codeDocumentTextInserted, text, insertPos);
  729. }
  730. }
  731. }
  732. //==============================================================================
  733. class CodeDocumentDeleteAction : public UndoableAction
  734. {
  735. public:
  736. CodeDocumentDeleteAction (CodeDocument& doc, const int start, const int end) noexcept
  737. : owner (doc), startPos (start), endPos (end),
  738. removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
  739. CodeDocument::Position (doc, end)))
  740. {
  741. }
  742. bool perform() override
  743. {
  744. owner.currentActionIndex++;
  745. owner.remove (startPos, endPos, false);
  746. return true;
  747. }
  748. bool undo() override
  749. {
  750. owner.currentActionIndex--;
  751. owner.insert (removedText, startPos, false);
  752. return true;
  753. }
  754. int getSizeInUnits() override { return (endPos - startPos) + 32; }
  755. private:
  756. CodeDocument& owner;
  757. const int startPos, endPos;
  758. const String removedText;
  759. JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction)
  760. };
  761. void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
  762. {
  763. if (endPos <= startPos)
  764. return;
  765. if (undoable)
  766. {
  767. undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos));
  768. }
  769. else
  770. {
  771. Position startPosition (*this, startPos);
  772. Position endPosition (*this, endPos);
  773. maximumLineLength = -1;
  774. const int firstAffectedLine = startPosition.getLineNumber();
  775. const int endLine = endPosition.getLineNumber();
  776. CodeDocumentLine& firstLine = *lines.getUnchecked (firstAffectedLine);
  777. if (firstAffectedLine == endLine)
  778. {
  779. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  780. + firstLine.line.substring (endPosition.getIndexInLine());
  781. firstLine.updateLength();
  782. }
  783. else
  784. {
  785. CodeDocumentLine& lastLine = *lines.getUnchecked (endLine);
  786. firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
  787. + lastLine.line.substring (endPosition.getIndexInLine());
  788. firstLine.updateLength();
  789. int numLinesToRemove = endLine - firstAffectedLine;
  790. lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
  791. }
  792. for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
  793. {
  794. CodeDocumentLine& l = *lines.getUnchecked (i);
  795. const CodeDocumentLine& previousLine = *lines.getUnchecked (i - 1);
  796. l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
  797. }
  798. checkLastLineStatus();
  799. const int totalChars = getNumCharacters();
  800. for (int i = 0; i < positionsToMaintain.size(); ++i)
  801. {
  802. CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
  803. if (p.getPosition() > startPosition.getPosition())
  804. p.setPosition (jmax (startPos, p.getPosition() + startPos - endPos));
  805. if (p.getPosition() > totalChars)
  806. p.setPosition (totalChars);
  807. }
  808. listeners.call (&CodeDocument::Listener::codeDocumentTextDeleted, startPos, endPos);
  809. }
  810. }
  811. } // namespace juce