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.

950 lines
27KB

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