mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-02-04 03:40:07 +00:00
942 lines
27 KiB
C++
942 lines
27 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
|
Copyright 2004-10 by Raw Material Software Ltd.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
JUCE can be redistributed and/or modified under the terms of the GNU General
|
|
Public License (Version 2), as published by the Free Software Foundation.
|
|
A copy of the license is included in the JUCE distribution, or can be found
|
|
online at www.gnu.org/licenses.
|
|
|
|
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
To release a closed-source product which uses JUCE, commercial licenses are
|
|
available: visit www.rawmaterialsoftware.com/juce for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#include "../../../core/juce_StandardHeader.h"
|
|
|
|
BEGIN_JUCE_NAMESPACE
|
|
|
|
#include "juce_CodeDocument.h"
|
|
|
|
|
|
//==============================================================================
|
|
class CodeDocumentLine
|
|
{
|
|
public:
|
|
CodeDocumentLine (const juce_wchar* const line_,
|
|
const int lineLength_,
|
|
const int numNewLineChars,
|
|
const int lineStartInFile_)
|
|
: line (line_, lineLength_),
|
|
lineStartInFile (lineStartInFile_),
|
|
lineLength (lineLength_),
|
|
lineLengthWithoutNewLines (lineLength_ - numNewLineChars)
|
|
{
|
|
}
|
|
|
|
~CodeDocumentLine()
|
|
{
|
|
}
|
|
|
|
static void createLines (Array <CodeDocumentLine*>& newLines, const String& text)
|
|
{
|
|
const juce_wchar* const t = text;
|
|
int pos = 0;
|
|
|
|
while (t [pos] != 0)
|
|
{
|
|
const int startOfLine = pos;
|
|
int numNewLineChars = 0;
|
|
|
|
while (t[pos] != 0)
|
|
{
|
|
if (t[pos] == '\r')
|
|
{
|
|
++numNewLineChars;
|
|
++pos;
|
|
|
|
if (t[pos] == '\n')
|
|
{
|
|
++numNewLineChars;
|
|
++pos;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (t[pos] == '\n')
|
|
{
|
|
++numNewLineChars;
|
|
++pos;
|
|
break;
|
|
}
|
|
|
|
++pos;
|
|
}
|
|
|
|
newLines.add (new CodeDocumentLine (t + startOfLine, pos - startOfLine,
|
|
numNewLineChars, startOfLine));
|
|
}
|
|
|
|
jassert (pos == text.length());
|
|
}
|
|
|
|
bool endsWithLineBreak() const throw()
|
|
{
|
|
return lineLengthWithoutNewLines != lineLength;
|
|
}
|
|
|
|
void updateLength() throw()
|
|
{
|
|
lineLengthWithoutNewLines = lineLength = line.length();
|
|
|
|
while (lineLengthWithoutNewLines > 0
|
|
&& (line [lineLengthWithoutNewLines - 1] == '\n'
|
|
|| line [lineLengthWithoutNewLines - 1] == '\r'))
|
|
{
|
|
--lineLengthWithoutNewLines;
|
|
}
|
|
}
|
|
|
|
String line;
|
|
int lineStartInFile, lineLength, lineLengthWithoutNewLines;
|
|
};
|
|
|
|
//==============================================================================
|
|
CodeDocument::Iterator::Iterator (CodeDocument* const document_)
|
|
: document (document_),
|
|
currentLine (document_->lines[0]),
|
|
line (0),
|
|
position (0)
|
|
{
|
|
}
|
|
|
|
CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other)
|
|
: document (other.document),
|
|
currentLine (other.currentLine),
|
|
line (other.line),
|
|
position (other.position)
|
|
{
|
|
}
|
|
|
|
CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) throw()
|
|
{
|
|
document = other.document;
|
|
currentLine = other.currentLine;
|
|
line = other.line;
|
|
position = other.position;
|
|
|
|
return *this;
|
|
}
|
|
|
|
CodeDocument::Iterator::~Iterator() throw()
|
|
{
|
|
}
|
|
|
|
juce_wchar CodeDocument::Iterator::nextChar()
|
|
{
|
|
if (currentLine == 0)
|
|
return 0;
|
|
|
|
jassert (currentLine == document->lines.getUnchecked (line));
|
|
const juce_wchar result = currentLine->line [position - currentLine->lineStartInFile];
|
|
|
|
if (++position >= currentLine->lineStartInFile + currentLine->lineLength)
|
|
{
|
|
++line;
|
|
currentLine = document->lines [line];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CodeDocument::Iterator::skip()
|
|
{
|
|
if (currentLine != 0)
|
|
{
|
|
jassert (currentLine == document->lines.getUnchecked (line));
|
|
|
|
if (++position >= currentLine->lineStartInFile + currentLine->lineLength)
|
|
{
|
|
++line;
|
|
currentLine = document->lines [line];
|
|
}
|
|
}
|
|
}
|
|
|
|
void CodeDocument::Iterator::skipToEndOfLine()
|
|
{
|
|
if (currentLine != 0)
|
|
{
|
|
jassert (currentLine == document->lines.getUnchecked (line));
|
|
|
|
++line;
|
|
currentLine = document->lines [line];
|
|
|
|
if (currentLine != 0)
|
|
position = currentLine->lineStartInFile;
|
|
else
|
|
position = document->getNumCharacters();
|
|
}
|
|
}
|
|
|
|
juce_wchar CodeDocument::Iterator::peekNextChar() const
|
|
{
|
|
if (currentLine == 0)
|
|
return 0;
|
|
|
|
jassert (currentLine == document->lines.getUnchecked (line));
|
|
return const_cast <const String&> (currentLine->line) [position - currentLine->lineStartInFile];
|
|
}
|
|
|
|
void CodeDocument::Iterator::skipWhitespace()
|
|
{
|
|
while (CharacterFunctions::isWhitespace (peekNextChar()))
|
|
skip();
|
|
}
|
|
|
|
bool CodeDocument::Iterator::isEOF() const throw()
|
|
{
|
|
return currentLine == 0;
|
|
}
|
|
|
|
//==============================================================================
|
|
CodeDocument::Position::Position() throw()
|
|
: owner (0), characterPos (0), line (0),
|
|
indexInLine (0), positionMaintained (false)
|
|
{
|
|
}
|
|
|
|
CodeDocument::Position::Position (const CodeDocument* const ownerDocument,
|
|
const int line_, const int indexInLine_) throw()
|
|
: owner (const_cast <CodeDocument*> (ownerDocument)),
|
|
characterPos (0), line (line_),
|
|
indexInLine (indexInLine_), positionMaintained (false)
|
|
{
|
|
setLineAndIndex (line_, indexInLine_);
|
|
}
|
|
|
|
CodeDocument::Position::Position (const CodeDocument* const ownerDocument,
|
|
const int characterPos_) throw()
|
|
: owner (const_cast <CodeDocument*> (ownerDocument)),
|
|
positionMaintained (false)
|
|
{
|
|
setPosition (characterPos_);
|
|
}
|
|
|
|
CodeDocument::Position::Position (const Position& other) throw()
|
|
: owner (other.owner), characterPos (other.characterPos), line (other.line),
|
|
indexInLine (other.indexInLine), positionMaintained (false)
|
|
{
|
|
jassert (*this == other);
|
|
}
|
|
|
|
CodeDocument::Position::~Position() throw()
|
|
{
|
|
setPositionMaintained (false);
|
|
}
|
|
|
|
CodeDocument::Position& CodeDocument::Position::operator= (const Position& other) throw()
|
|
{
|
|
if (this != &other)
|
|
{
|
|
const bool wasPositionMaintained = positionMaintained;
|
|
if (owner != other.owner)
|
|
setPositionMaintained (false);
|
|
|
|
owner = other.owner;
|
|
line = other.line;
|
|
indexInLine = other.indexInLine;
|
|
characterPos = other.characterPos;
|
|
setPositionMaintained (wasPositionMaintained);
|
|
|
|
jassert (*this == other);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool CodeDocument::Position::operator== (const Position& other) const throw()
|
|
{
|
|
jassert ((characterPos == other.characterPos)
|
|
== (line == other.line && indexInLine == other.indexInLine));
|
|
|
|
return characterPos == other.characterPos
|
|
&& line == other.line
|
|
&& indexInLine == other.indexInLine
|
|
&& owner == other.owner;
|
|
}
|
|
|
|
bool CodeDocument::Position::operator!= (const Position& other) const throw()
|
|
{
|
|
return ! operator== (other);
|
|
}
|
|
|
|
void CodeDocument::Position::setLineAndIndex (const int newLine, const int newIndexInLine) throw()
|
|
{
|
|
jassert (owner != 0);
|
|
|
|
if (owner->lines.size() == 0)
|
|
{
|
|
line = 0;
|
|
indexInLine = 0;
|
|
characterPos = 0;
|
|
}
|
|
else
|
|
{
|
|
if (newLine >= owner->lines.size())
|
|
{
|
|
line = owner->lines.size() - 1;
|
|
|
|
CodeDocumentLine* const l = owner->lines.getUnchecked (line);
|
|
jassert (l != 0);
|
|
|
|
indexInLine = l->lineLengthWithoutNewLines;
|
|
characterPos = l->lineStartInFile + indexInLine;
|
|
}
|
|
else
|
|
{
|
|
line = jmax (0, newLine);
|
|
|
|
CodeDocumentLine* const l = owner->lines.getUnchecked (line);
|
|
jassert (l != 0);
|
|
|
|
if (l->lineLengthWithoutNewLines > 0)
|
|
indexInLine = jlimit (0, l->lineLengthWithoutNewLines, newIndexInLine);
|
|
else
|
|
indexInLine = 0;
|
|
|
|
characterPos = l->lineStartInFile + indexInLine;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CodeDocument::Position::setPosition (const int newPosition) throw()
|
|
{
|
|
jassert (owner != 0);
|
|
|
|
line = 0;
|
|
indexInLine = 0;
|
|
characterPos = 0;
|
|
|
|
if (newPosition > 0)
|
|
{
|
|
int lineStart = 0;
|
|
int lineEnd = owner->lines.size();
|
|
|
|
for (;;)
|
|
{
|
|
if (lineEnd - lineStart < 4)
|
|
{
|
|
for (int i = lineStart; i < lineEnd; ++i)
|
|
{
|
|
CodeDocumentLine* const l = owner->lines.getUnchecked (i);
|
|
|
|
int index = newPosition - l->lineStartInFile;
|
|
|
|
if (index >= 0 && (index < l->lineLength || i == lineEnd - 1))
|
|
{
|
|
line = i;
|
|
indexInLine = jmin (l->lineLengthWithoutNewLines, index);
|
|
characterPos = l->lineStartInFile + indexInLine;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
const int midIndex = (lineStart + lineEnd + 1) / 2;
|
|
CodeDocumentLine* const mid = owner->lines.getUnchecked (midIndex);
|
|
|
|
if (newPosition >= mid->lineStartInFile)
|
|
lineStart = midIndex;
|
|
else
|
|
lineEnd = midIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CodeDocument::Position::moveBy (int characterDelta) throw()
|
|
{
|
|
jassert (owner != 0);
|
|
|
|
if (characterDelta == 1)
|
|
{
|
|
setPosition (getPosition());
|
|
|
|
// If moving right, make sure we don't get stuck between the \r and \n characters..
|
|
if (line < owner->lines.size())
|
|
{
|
|
CodeDocumentLine* const l = owner->lines.getUnchecked (line);
|
|
if (indexInLine + characterDelta < l->lineLength
|
|
&& indexInLine + characterDelta >= l->lineLengthWithoutNewLines + 1)
|
|
++characterDelta;
|
|
}
|
|
}
|
|
|
|
setPosition (characterPos + characterDelta);
|
|
}
|
|
|
|
const CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const throw()
|
|
{
|
|
CodeDocument::Position p (*this);
|
|
p.moveBy (characterDelta);
|
|
return p;
|
|
}
|
|
|
|
const CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const throw()
|
|
{
|
|
CodeDocument::Position p (*this);
|
|
p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine());
|
|
return p;
|
|
}
|
|
|
|
const juce_wchar CodeDocument::Position::getCharacter() const throw()
|
|
{
|
|
const CodeDocumentLine* const l = owner->lines [line];
|
|
return l == 0 ? 0 : l->line [getIndexInLine()];
|
|
}
|
|
|
|
const String CodeDocument::Position::getLineText() const throw()
|
|
{
|
|
const CodeDocumentLine* const l = owner->lines [line];
|
|
return l == 0 ? String::empty : l->line;
|
|
}
|
|
|
|
void CodeDocument::Position::setPositionMaintained (const bool isMaintained) throw()
|
|
{
|
|
if (isMaintained != positionMaintained)
|
|
{
|
|
positionMaintained = isMaintained;
|
|
|
|
if (owner != 0)
|
|
{
|
|
if (isMaintained)
|
|
{
|
|
jassert (! owner->positionsToMaintain.contains (this));
|
|
owner->positionsToMaintain.add (this);
|
|
}
|
|
else
|
|
{
|
|
// If this happens, you may have deleted the document while there are Position objects that are still using it...
|
|
jassert (owner->positionsToMaintain.contains (this));
|
|
owner->positionsToMaintain.removeValue (this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
CodeDocument::CodeDocument()
|
|
: undoManager (std::numeric_limits<int>::max(), 10000),
|
|
currentActionIndex (0),
|
|
indexOfSavedState (-1),
|
|
maximumLineLength (-1),
|
|
newLineChars ("\r\n")
|
|
{
|
|
}
|
|
|
|
CodeDocument::~CodeDocument()
|
|
{
|
|
}
|
|
|
|
const String CodeDocument::getAllContent() const throw()
|
|
{
|
|
return getTextBetween (Position (this, 0),
|
|
Position (this, lines.size(), 0));
|
|
}
|
|
|
|
const String CodeDocument::getTextBetween (const Position& start, const Position& end) const throw()
|
|
{
|
|
if (end.getPosition() <= start.getPosition())
|
|
return String::empty;
|
|
|
|
const int startLine = start.getLineNumber();
|
|
const int endLine = end.getLineNumber();
|
|
|
|
if (startLine == endLine)
|
|
{
|
|
CodeDocumentLine* const line = lines [startLine];
|
|
return (line == 0) ? String::empty : line->line.substring (start.getIndexInLine(), end.getIndexInLine());
|
|
}
|
|
|
|
String result;
|
|
result.preallocateStorage (end.getPosition() - start.getPosition() + 4);
|
|
String::Concatenator concatenator (result);
|
|
|
|
const int maxLine = jmin (lines.size() - 1, endLine);
|
|
|
|
for (int i = jmax (0, startLine); i <= maxLine; ++i)
|
|
{
|
|
const CodeDocumentLine* line = lines.getUnchecked(i);
|
|
int len = line->lineLength;
|
|
|
|
if (i == startLine)
|
|
{
|
|
const int index = start.getIndexInLine();
|
|
concatenator.append (line->line.substring (index, len));
|
|
}
|
|
else if (i == endLine)
|
|
{
|
|
len = end.getIndexInLine();
|
|
concatenator.append (line->line.substring (0, len));
|
|
}
|
|
else
|
|
{
|
|
concatenator.append (line->line);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int CodeDocument::getNumCharacters() const throw()
|
|
{
|
|
const CodeDocumentLine* const lastLine = lines.getLast();
|
|
return (lastLine == 0) ? 0 : lastLine->lineStartInFile + lastLine->lineLength;
|
|
}
|
|
|
|
const String CodeDocument::getLine (const int lineIndex) const throw()
|
|
{
|
|
const CodeDocumentLine* const line = lines [lineIndex];
|
|
return (line == 0) ? String::empty : line->line;
|
|
}
|
|
|
|
int CodeDocument::getMaximumLineLength() throw()
|
|
{
|
|
if (maximumLineLength < 0)
|
|
{
|
|
maximumLineLength = 0;
|
|
|
|
for (int i = lines.size(); --i >= 0;)
|
|
maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength);
|
|
}
|
|
|
|
return maximumLineLength;
|
|
}
|
|
|
|
void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition)
|
|
{
|
|
remove (startPosition.getPosition(), endPosition.getPosition(), true);
|
|
}
|
|
|
|
void CodeDocument::insertText (const Position& position, const String& text)
|
|
{
|
|
insert (text, position.getPosition(), true);
|
|
}
|
|
|
|
void CodeDocument::replaceAllContent (const String& newContent)
|
|
{
|
|
remove (0, getNumCharacters(), true);
|
|
insert (newContent, 0, true);
|
|
}
|
|
|
|
bool CodeDocument::loadFromStream (InputStream& stream)
|
|
{
|
|
replaceAllContent (stream.readEntireStreamAsString());
|
|
setSavePoint();
|
|
clearUndoHistory();
|
|
return true;
|
|
}
|
|
|
|
bool CodeDocument::writeToStream (OutputStream& stream)
|
|
{
|
|
for (int i = 0; i < lines.size(); ++i)
|
|
{
|
|
String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string.
|
|
const char* utf8 = temp.toUTF8();
|
|
|
|
if (! stream.write (utf8, (int) strlen (utf8)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CodeDocument::setNewLineCharacters (const String& newLine) throw()
|
|
{
|
|
jassert (newLine == "\r\n" || newLine == "\n" || newLine == "\r");
|
|
newLineChars = newLine;
|
|
}
|
|
|
|
void CodeDocument::newTransaction()
|
|
{
|
|
undoManager.beginNewTransaction (String::empty);
|
|
}
|
|
|
|
void CodeDocument::undo()
|
|
{
|
|
newTransaction();
|
|
undoManager.undo();
|
|
}
|
|
|
|
void CodeDocument::redo()
|
|
{
|
|
undoManager.redo();
|
|
}
|
|
|
|
void CodeDocument::clearUndoHistory()
|
|
{
|
|
undoManager.clearUndoHistory();
|
|
}
|
|
|
|
void CodeDocument::setSavePoint() throw()
|
|
{
|
|
indexOfSavedState = currentActionIndex;
|
|
}
|
|
|
|
bool CodeDocument::hasChangedSinceSavePoint() const throw()
|
|
{
|
|
return currentActionIndex != indexOfSavedState;
|
|
}
|
|
|
|
//==============================================================================
|
|
static int getCodeCharacterCategory (const juce_wchar character) throw()
|
|
{
|
|
return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
|
|
? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
|
|
}
|
|
|
|
const CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const throw()
|
|
{
|
|
Position p (position);
|
|
const int maxDistance = 256;
|
|
int i = 0;
|
|
|
|
while (i < maxDistance
|
|
&& CharacterFunctions::isWhitespace (p.getCharacter())
|
|
&& (i == 0 || (p.getCharacter() != '\n'
|
|
&& p.getCharacter() != '\r')))
|
|
{
|
|
++i;
|
|
p.moveBy (1);
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
const int type = getCodeCharacterCategory (p.getCharacter());
|
|
|
|
while (i < maxDistance && type == getCodeCharacterCategory (p.getCharacter()))
|
|
{
|
|
++i;
|
|
p.moveBy (1);
|
|
}
|
|
|
|
while (i < maxDistance
|
|
&& CharacterFunctions::isWhitespace (p.getCharacter())
|
|
&& (i == 0 || (p.getCharacter() != '\n'
|
|
&& p.getCharacter() != '\r')))
|
|
{
|
|
++i;
|
|
p.moveBy (1);
|
|
}
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
const CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const throw()
|
|
{
|
|
Position p (position);
|
|
const int maxDistance = 256;
|
|
int i = 0;
|
|
bool stoppedAtLineStart = false;
|
|
|
|
while (i < maxDistance)
|
|
{
|
|
const juce_wchar c = p.movedBy (-1).getCharacter();
|
|
|
|
if (c == '\r' || c == '\n')
|
|
{
|
|
stoppedAtLineStart = true;
|
|
|
|
if (i > 0)
|
|
break;
|
|
}
|
|
|
|
if (! CharacterFunctions::isWhitespace (c))
|
|
break;
|
|
|
|
p.moveBy (-1);
|
|
++i;
|
|
}
|
|
|
|
if (i < maxDistance && ! stoppedAtLineStart)
|
|
{
|
|
const int type = getCodeCharacterCategory (p.movedBy (-1).getCharacter());
|
|
|
|
while (i < maxDistance && type == getCodeCharacterCategory (p.movedBy (-1).getCharacter()))
|
|
{
|
|
p.moveBy (-1);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
void CodeDocument::checkLastLineStatus()
|
|
{
|
|
while (lines.size() > 0
|
|
&& lines.getLast()->lineLength == 0
|
|
&& (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
|
|
{
|
|
// remove any empty lines at the end if the preceding line doesn't end in a newline.
|
|
lines.removeLast();
|
|
}
|
|
|
|
const CodeDocumentLine* const lastLine = lines.getLast();
|
|
|
|
if (lastLine != 0 && lastLine->endsWithLineBreak())
|
|
{
|
|
// check that there's an empty line at the end if the preceding one ends in a newline..
|
|
lines.add (new CodeDocumentLine (String::empty, 0, 0, lastLine->lineStartInFile + lastLine->lineLength));
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void CodeDocument::addListener (CodeDocument::Listener* const listener) throw()
|
|
{
|
|
listeners.add (listener);
|
|
}
|
|
|
|
void CodeDocument::removeListener (CodeDocument::Listener* const listener) throw()
|
|
{
|
|
listeners.remove (listener);
|
|
}
|
|
|
|
void CodeDocument::sendListenerChangeMessage (const int startLine, const int endLine)
|
|
{
|
|
Position startPos (this, startLine, 0);
|
|
Position endPos (this, endLine, 0);
|
|
|
|
listeners.call (&Listener::codeDocumentChanged, startPos, endPos);
|
|
}
|
|
|
|
//==============================================================================
|
|
class CodeDocumentInsertAction : public UndoableAction
|
|
{
|
|
CodeDocument& owner;
|
|
const String text;
|
|
int insertPos;
|
|
|
|
CodeDocumentInsertAction (const CodeDocumentInsertAction&);
|
|
CodeDocumentInsertAction& operator= (const CodeDocumentInsertAction&);
|
|
|
|
public:
|
|
CodeDocumentInsertAction (CodeDocument& owner_, const String& text_, const int insertPos_) throw()
|
|
: owner (owner_),
|
|
text (text_),
|
|
insertPos (insertPos_)
|
|
{
|
|
}
|
|
|
|
~CodeDocumentInsertAction() {}
|
|
|
|
bool perform()
|
|
{
|
|
owner.currentActionIndex++;
|
|
owner.insert (text, insertPos, false);
|
|
return true;
|
|
}
|
|
|
|
bool undo()
|
|
{
|
|
owner.currentActionIndex--;
|
|
owner.remove (insertPos, insertPos + text.length(), false);
|
|
return true;
|
|
}
|
|
|
|
int getSizeInUnits() { return text.length() + 32; }
|
|
};
|
|
|
|
void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
|
|
{
|
|
if (text.isEmpty())
|
|
return;
|
|
|
|
if (undoable)
|
|
{
|
|
undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos));
|
|
}
|
|
else
|
|
{
|
|
Position pos (this, insertPos);
|
|
const int firstAffectedLine = pos.getLineNumber();
|
|
int lastAffectedLine = firstAffectedLine + 1;
|
|
|
|
CodeDocumentLine* const firstLine = lines [firstAffectedLine];
|
|
String textInsideOriginalLine (text);
|
|
|
|
if (firstLine != 0)
|
|
{
|
|
const int index = pos.getIndexInLine();
|
|
textInsideOriginalLine = firstLine->line.substring (0, index)
|
|
+ textInsideOriginalLine
|
|
+ firstLine->line.substring (index);
|
|
}
|
|
|
|
maximumLineLength = -1;
|
|
Array <CodeDocumentLine*> newLines;
|
|
CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
|
|
jassert (newLines.size() > 0);
|
|
|
|
CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0);
|
|
newFirstLine->lineStartInFile = firstLine != 0 ? firstLine->lineStartInFile : 0;
|
|
lines.set (firstAffectedLine, newFirstLine);
|
|
|
|
if (newLines.size() > 1)
|
|
{
|
|
for (int i = 1; i < newLines.size(); ++i)
|
|
{
|
|
CodeDocumentLine* const l = newLines.getUnchecked (i);
|
|
lines.insert (firstAffectedLine + i, l);
|
|
}
|
|
|
|
lastAffectedLine = lines.size();
|
|
}
|
|
|
|
int i, lineStart = newFirstLine->lineStartInFile;
|
|
for (i = firstAffectedLine; i < lines.size(); ++i)
|
|
{
|
|
CodeDocumentLine* const l = lines.getUnchecked (i);
|
|
l->lineStartInFile = lineStart;
|
|
lineStart += l->lineLength;
|
|
}
|
|
|
|
checkLastLineStatus();
|
|
|
|
const int newTextLength = text.length();
|
|
for (i = 0; i < positionsToMaintain.size(); ++i)
|
|
{
|
|
CodeDocument::Position* const p = positionsToMaintain.getUnchecked(i);
|
|
|
|
if (p->getPosition() >= insertPos)
|
|
p->setPosition (p->getPosition() + newTextLength);
|
|
}
|
|
|
|
sendListenerChangeMessage (firstAffectedLine, lastAffectedLine);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
class CodeDocumentDeleteAction : public UndoableAction
|
|
{
|
|
CodeDocument& owner;
|
|
int startPos, endPos;
|
|
String removedText;
|
|
|
|
CodeDocumentDeleteAction (const CodeDocumentDeleteAction&);
|
|
CodeDocumentDeleteAction& operator= (const CodeDocumentDeleteAction&);
|
|
|
|
public:
|
|
CodeDocumentDeleteAction (CodeDocument& owner_, const int startPos_, const int endPos_) throw()
|
|
: owner (owner_),
|
|
startPos (startPos_),
|
|
endPos (endPos_)
|
|
{
|
|
removedText = owner.getTextBetween (CodeDocument::Position (&owner, startPos),
|
|
CodeDocument::Position (&owner, endPos));
|
|
}
|
|
|
|
~CodeDocumentDeleteAction() {}
|
|
|
|
bool perform()
|
|
{
|
|
owner.currentActionIndex++;
|
|
owner.remove (startPos, endPos, false);
|
|
return true;
|
|
}
|
|
|
|
bool undo()
|
|
{
|
|
owner.currentActionIndex--;
|
|
owner.insert (removedText, startPos, false);
|
|
return true;
|
|
}
|
|
|
|
int getSizeInUnits() { return removedText.length() + 32; }
|
|
};
|
|
|
|
void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
|
|
{
|
|
if (endPos <= startPos)
|
|
return;
|
|
|
|
if (undoable)
|
|
{
|
|
undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos));
|
|
}
|
|
else
|
|
{
|
|
Position startPosition (this, startPos);
|
|
Position endPosition (this, endPos);
|
|
|
|
maximumLineLength = -1;
|
|
const int firstAffectedLine = startPosition.getLineNumber();
|
|
const int endLine = endPosition.getLineNumber();
|
|
int lastAffectedLine = firstAffectedLine + 1;
|
|
CodeDocumentLine* const firstLine = lines.getUnchecked (firstAffectedLine);
|
|
|
|
if (firstAffectedLine == endLine)
|
|
{
|
|
firstLine->line = firstLine->line.substring (0, startPosition.getIndexInLine())
|
|
+ firstLine->line.substring (endPosition.getIndexInLine());
|
|
firstLine->updateLength();
|
|
}
|
|
else
|
|
{
|
|
lastAffectedLine = lines.size();
|
|
|
|
CodeDocumentLine* const lastLine = lines.getUnchecked (endLine);
|
|
jassert (lastLine != 0);
|
|
|
|
firstLine->line = firstLine->line.substring (0, startPosition.getIndexInLine())
|
|
+ lastLine->line.substring (endPosition.getIndexInLine());
|
|
firstLine->updateLength();
|
|
|
|
int numLinesToRemove = endLine - firstAffectedLine;
|
|
lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
|
|
}
|
|
|
|
int i;
|
|
for (i = firstAffectedLine + 1; i < lines.size(); ++i)
|
|
{
|
|
CodeDocumentLine* const l = lines.getUnchecked (i);
|
|
const CodeDocumentLine* const previousLine = lines.getUnchecked (i - 1);
|
|
l->lineStartInFile = previousLine->lineStartInFile + previousLine->lineLength;
|
|
}
|
|
|
|
checkLastLineStatus();
|
|
|
|
const int totalChars = getNumCharacters();
|
|
|
|
for (i = 0; i < positionsToMaintain.size(); ++i)
|
|
{
|
|
CodeDocument::Position* p = positionsToMaintain.getUnchecked(i);
|
|
|
|
if (p->getPosition() > startPosition.getPosition())
|
|
p->setPosition (jmax (startPos, p->getPosition() + startPos - endPos));
|
|
|
|
if (p->getPosition() > totalChars)
|
|
p->setPosition (totalChars);
|
|
}
|
|
|
|
sendListenerChangeMessage (firstAffectedLine, lastAffectedLine);
|
|
}
|
|
}
|
|
|
|
END_JUCE_NAMESPACE
|