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.

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