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.

947 lines
26KB

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