RedPanda-CPP/RedPandaIDE/qsynedit/TextBuffer.cpp

1154 lines
29 KiB
C++

/*
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "TextBuffer.h"
#include <QDataStream>
#include <QFile>
#include <QTextCodec>
#include <QTextStream>
#include <QMutexLocker>
#include <stdexcept>
#include "SynEdit.h"
#include "../utils.h"
#include "../platform.h"
#include <QMessageBox>
#include <cmath>
SynDocument::SynDocument(const QFont& font, QObject *parent):
QObject(parent),
mFontMetrics(font),
mTabWidth(4),
mMutex(QMutex::Recursive)
{
mAppendNewLineAtEOF = true;
mFileEndingType = FileEndingType::Windows;
mIndexOfLongestLine = -1;
mUpdateCount = 0;
mCharWidth = mFontMetrics.horizontalAdvance("M");
}
static void ListIndexOutOfBounds(int index) {
throw IndexOutOfRange(index);
}
int SynDocument::parenthesisLevels(int Index)
{
QMutexLocker locker(&mMutex);
if (Index>=0 && Index < mLines.size()) {
return mLines[Index]->fRange.parenthesisLevel;
} else
return 0;
}
int SynDocument::bracketLevels(int Index)
{
QMutexLocker locker(&mMutex);
if (Index>=0 && Index < mLines.size()) {
return mLines[Index]->fRange.bracketLevel;
} else
return 0;
}
int SynDocument::braceLevels(int Index)
{
QMutexLocker locker(&mMutex);
if (Index>=0 && Index < mLines.size()) {
return mLines[Index]->fRange.braceLevel;
} else
return 0;
}
//QString SynEditStringList::expandedStrings(int Index)
//{
// if (Index>=0 && Index < mList.size()) {
// if (mList[Index]->fFlags & SynEditStringFlag::sfHasNoTabs)
// return mList[Index]->fString;
// else
// return ExpandString(Index);
// } else
// return QString();
//}
int SynDocument::lineColumns(int Index)
{
QMutexLocker locker(&mMutex);
if (Index>=0 && Index < mLines.size()) {
if (mLines[Index]->fColumns == -1) {
return calculateLineColumns(Index);
} else
return mLines[Index]->fColumns;
} else
return 0;
}
int SynDocument::leftBraces(int Index)
{
QMutexLocker locker(&mMutex);
if (Index>=0 && Index < mLines.size()) {
return mLines[Index]->fRange.leftBraces;
} else
return 0;
}
int SynDocument::rightBraces(int Index)
{
QMutexLocker locker(&mMutex);
if (Index>=0 && Index < mLines.size()) {
return mLines[Index]->fRange.rightBraces;
} else
return 0;
}
int SynDocument::lengthOfLongestLine() {
QMutexLocker locker(&mMutex);
if (mIndexOfLongestLine < 0) {
int MaxLen = -1;
mIndexOfLongestLine = -1;
if (mLines.count() > 0 ) {
for (int i=0;i<mLines.size();i++) {
int len = lineColumns(i);
if (len > MaxLen) {
MaxLen = len;
mIndexOfLongestLine = i;
}
}
}
}
if (mIndexOfLongestLine >= 0)
return mLines[mIndexOfLongestLine]->fColumns;
else
return 0;
}
QString SynDocument::lineBreak() const
{
switch(mFileEndingType) {
case FileEndingType::Linux:
return "\n";
case FileEndingType::Windows:
return "\r\n";
case FileEndingType::Mac:
return "\r";
}
return "\n";
}
SynRangeState SynDocument::ranges(int Index)
{
QMutexLocker locker(&mMutex);
if (Index>=0 && Index < mLines.size()) {
return mLines[Index]->fRange;
} else {
ListIndexOutOfBounds(Index);
}
return SynRangeState();
}
void SynDocument::insertItem(int Index, const QString &s)
{
beginUpdate();
PSynDocumentLine line = std::make_shared<SynDocumentLine>();
line->fString = s;
mIndexOfLongestLine = -1;
mLines.insert(Index,line);
endUpdate();
}
void SynDocument::addItem(const QString &s)
{
beginUpdate();
PSynDocumentLine line = std::make_shared<SynDocumentLine>();
line->fString = s;
mIndexOfLongestLine = -1;
mLines.append(line);
endUpdate();
}
bool SynDocument::getAppendNewLineAtEOF()
{
QMutexLocker locker(&mMutex);
return mAppendNewLineAtEOF;
}
void SynDocument::setAppendNewLineAtEOF(bool appendNewLineAtEOF)
{
QMutexLocker locker(&mMutex);
mAppendNewLineAtEOF = appendNewLineAtEOF;
}
void SynDocument::setRange(int Index, const SynRangeState& ARange)
{
QMutexLocker locker(&mMutex);
if (Index<0 || Index>=mLines.count()) {
ListIndexOutOfBounds(Index);
}
beginUpdate();
mLines[Index]->fRange = ARange;
endUpdate();
}
QString SynDocument::getString(int Index)
{
QMutexLocker locker(&mMutex);
if (Index<0 || Index>=mLines.count()) {
return QString();
}
return mLines[Index]->fString;
}
int SynDocument::count()
{
QMutexLocker locker(&mMutex);
return mLines.count();
}
QString SynDocument::text()
{
QMutexLocker locker(&mMutex);
return getTextStr();
}
void SynDocument::setText(const QString &text)
{
QMutexLocker locker(&mMutex);
putTextStr(text);
}
void SynDocument::setContents(const QStringList &text)
{
QMutexLocker locker(&mMutex);
beginUpdate();
auto action = finally([this]{
endUpdate();
});
internalClear();
if (text.count() > 0) {
mIndexOfLongestLine = -1;
int FirstAdded = mLines.count();
foreach (const QString& s,text) {
addItem(s);
}
emit inserted(FirstAdded,text.count());
}
}
QStringList SynDocument::contents()
{
QMutexLocker locker(&mMutex);
QStringList Result;
SynDocumentLines list = mLines;
foreach (const PSynDocumentLine& line, list) {
Result.append(line->fString);
}
return Result;
}
void SynDocument::beginUpdate()
{
if (mUpdateCount == 0) {
setUpdateState(true);
}
mUpdateCount++;
}
void SynDocument::endUpdate()
{
mUpdateCount--;
if (mUpdateCount == 0) {
setUpdateState(false);
}
}
int SynDocument::add(const QString &s)
{
QMutexLocker locker(&mMutex);
beginUpdate();
int Result = mLines.count();
insertItem(Result, s);
emit inserted(Result,1);
endUpdate();
return Result;
}
void SynDocument::addStrings(const QStringList &Strings)
{
QMutexLocker locker(&mMutex);
if (Strings.count() > 0) {
mIndexOfLongestLine = -1;
beginUpdate();
auto action = finally([this]{
endUpdate();
});
int FirstAdded = mLines.count();
for (const QString& s:Strings) {
addItem(s);
}
emit inserted(FirstAdded,Strings.count());
}
}
int SynDocument::getTextLength()
{
QMutexLocker locker(&mMutex);
int Result = 0;
foreach (const PSynDocumentLine& line, mLines ) {
Result += line->fString.length();
if (mFileEndingType == FileEndingType::Windows) {
Result += 2;
} else {
Result += 1;
}
}
return Result;
}
void SynDocument::clear()
{
QMutexLocker locker(&mMutex);
internalClear();
}
void SynDocument::deleteLines(int Index, int NumLines)
{
QMutexLocker locker(&mMutex);
if (NumLines<=0)
return;
if ((Index < 0) || (Index >= mLines.count())) {
ListIndexOutOfBounds(Index);
}
beginUpdate();
auto action = finally([this]{
endUpdate();
});
if (mIndexOfLongestLine>=Index) {
if (mIndexOfLongestLine <Index+NumLines) {
mIndexOfLongestLine = -1;
} else {
mIndexOfLongestLine -= NumLines;
}
}
int LinesAfter = mLines.count() - (Index + NumLines);
if (LinesAfter < 0) {
NumLines = mLines.count() - Index;
}
mLines.remove(Index,NumLines);
emit deleted(Index,NumLines);
}
void SynDocument::exchange(int Index1, int Index2)
{
QMutexLocker locker(&mMutex);
if ((Index1 < 0) || (Index1 >= mLines.count())) {
ListIndexOutOfBounds(Index1);
}
if ((Index2 < 0) || (Index2 >= mLines.count())) {
ListIndexOutOfBounds(Index2);
}
beginUpdate();
PSynDocumentLine temp = mLines[Index1];
mLines[Index1]=mLines[Index2];
mLines[Index2]=temp;
//mList.swapItemsAt(Index1,Index2);
if (mIndexOfLongestLine == Index1) {
mIndexOfLongestLine = Index2;
} else if (mIndexOfLongestLine == Index2) {
mIndexOfLongestLine = Index1;
}
endUpdate();
}
void SynDocument::insert(int Index, const QString &s)
{
QMutexLocker locker(&mMutex);
if ((Index < 0) || (Index > mLines.count())) {
ListIndexOutOfBounds(Index);
}
beginUpdate();
insertItem(Index, s);
emit inserted(Index,1);
endUpdate();
}
void SynDocument::deleteAt(int Index)
{
QMutexLocker locker(&mMutex);
if ((Index < 0) || (Index >= mLines.count())) {
ListIndexOutOfBounds(Index);
}
beginUpdate();
if (mIndexOfLongestLine == Index)
mIndexOfLongestLine = -1;
else if (mIndexOfLongestLine>Index)
mIndexOfLongestLine -= 1;
mLines.removeAt(Index);
emit deleted(Index,1);
endUpdate();
}
QString SynDocument::getTextStr() const
{
QString result;
for (int i=0;i<mLines.count()-1;i++) {
const PSynDocumentLine& line = mLines[i];
result.append(line->fString);
result.append(lineBreak());
}
if (mLines.length()>0) {
result.append(mLines.back()->fString);
}
return result;
}
void SynDocument::putString(int Index, const QString &s, bool notify) {
QMutexLocker locker(&mMutex);
if (Index == mLines.count()) {
add(s);
} else {
if (Index<0 || Index>=mLines.count()) {
ListIndexOutOfBounds(Index);
}
beginUpdate();
int oldColumns = mLines[Index]->fColumns;
mLines[Index]->fString = s;
calculateLineColumns(Index);
if (mIndexOfLongestLine == Index && oldColumns>mLines[Index]->fColumns )
mIndexOfLongestLine = -1;
else if (mIndexOfLongestLine>=0
&& mIndexOfLongestLine<mLines.count()
&& mLines[Index]->fColumns > mLines[mIndexOfLongestLine]->fColumns)
mIndexOfLongestLine = Index;
if (notify)
emit putted(Index,1);
endUpdate();
}
}
void SynDocument::setUpdateState(bool Updating)
{
if (Updating)
emit changing();
else
emit changed();
}
int SynDocument::calculateLineColumns(int Index)
{
PSynDocumentLine line = mLines[Index];
line->fColumns = stringColumns(line->fString,0);
return line->fColumns;
}
void SynDocument::insertLines(int Index, int NumLines)
{
QMutexLocker locker(&mMutex);
if (Index<0 || Index>mLines.count()) {
ListIndexOutOfBounds(Index);
}
if (NumLines<=0)
return;
beginUpdate();
auto action = finally([this]{
endUpdate();
});
mIndexOfLongestLine = -1;
PSynDocumentLine line;
mLines.insert(Index,NumLines,line);
for (int i=Index;i<Index+NumLines;i++) {
line = std::make_shared<SynDocumentLine>();
mLines[i]=line;
}
emit inserted(Index,NumLines);
}
bool SynDocument::tryLoadFileByEncoding(QByteArray encodingName, QFile& file) {
QTextCodec* codec = QTextCodec::codecForName(encodingName);
if (!codec)
return false;
file.reset();
internalClear();
QTextCodec::ConverterState state;
while (true) {
if (file.atEnd()){
break;
}
QByteArray line = file.readLine();
if (line.endsWith("\r\n")) {
line.remove(line.length()-2,2);
} else if (line.endsWith("\r")) {
line.remove(line.length()-1,1);
} else if (line.endsWith("\n")){
line.remove(line.length()-1,1);
}
QString newLine = codec->toUnicode(line.constData(),line.length(),&state);
if (state.invalidChars>0) {
return false;
break;
}
addItem(newLine);
}
return true;
}
const QFontMetrics &SynDocument::fontMetrics() const
{
return mFontMetrics;
}
void SynDocument::setFontMetrics(const QFont &newFont)
{
mFontMetrics = QFontMetrics(newFont);
mCharWidth = mFontMetrics.horizontalAdvance("M");
}
void SynDocument::setTabWidth(int newTabWidth)
{
if (mTabWidth!=newTabWidth) {
mTabWidth = newTabWidth;
resetColumns();
}
}
void SynDocument::loadFromFile(const QString& filename, const QByteArray& encoding, QByteArray& realEncoding)
{
QMutexLocker locker(&mMutex);
QFile file(filename);
if (!file.open(QFile::ReadOnly ))
throw FileError(tr("Can't open file '%1' for read!").arg(file.fileName()));
beginUpdate();
internalClear();
auto action = finally([this]{
if (mLines.count()>0)
emit inserted(0,mLines.count());
endUpdate();
});
mIndexOfLongestLine = -1;
//test for utf8 / utf 8 bom
if (encoding == ENCODING_AUTO_DETECT) {
if (file.atEnd()) {
realEncoding = ENCODING_ASCII;
return;
}
QByteArray line = file.readLine();
QTextCodec* codec;
QTextCodec::ConverterState state;
bool needReread = false;
bool allAscii = true;
//test for BOM
if ((line.length()>=3) && ((unsigned char)line[0]==0xEF) && ((unsigned char)line[1]==0xBB) && ((unsigned char)line[2]==0xBF) ) {
realEncoding = ENCODING_UTF8_BOM;
line = line.mid(3);
codec = QTextCodec::codecForName(ENCODING_UTF8);
} else {
realEncoding = ENCODING_UTF8;
codec = QTextCodec::codecForName(ENCODING_UTF8);
}
if (line.endsWith("\r\n")) {
mFileEndingType = FileEndingType::Windows;
} else if (line.endsWith("\n")) {
mFileEndingType = FileEndingType::Linux;
} else if (line.endsWith("\r")) {
mFileEndingType = FileEndingType::Mac;
}
internalClear();
while (true) {
if (line.endsWith("\r\n")) {
line.remove(line.length()-2,2);
} else if (line.endsWith("\r")) {
line.remove(line.length()-1,1);
} else if (line.endsWith("\n")){
line.remove(line.length()-1,1);
}
if (allAscii) {
allAscii = isTextAllAscii(line);
}
if (allAscii) {
addItem(QString::fromLatin1(line));
} else {
QString newLine = codec->toUnicode(line.constData(),line.length(),&state);
if (state.invalidChars>0) {
needReread = true;
break;
}
addItem(newLine);
}
if (file.atEnd()){
break;
}
line = file.readLine();
}
if (!needReread) {
if (allAscii)
realEncoding = ENCODING_ASCII;
return;
}
realEncoding = pCharsetInfoManager->getDefaultSystemEncoding();
QList<PCharsetInfo> charsets = pCharsetInfoManager->findCharsetByLocale(pCharsetInfoManager->localeName());
if (!charsets.isEmpty()) {
if (tryLoadFileByEncoding(realEncoding,file)) {
return;
}
QSet<QByteArray> encodingSet;
for (int i=0;i<charsets.size();i++) {
encodingSet.insert(charsets[i]->name);
}
encodingSet.remove(realEncoding);
foreach (const QByteArray& encodingName,encodingSet) {
if (encodingName == ENCODING_UTF8)
continue;
if (tryLoadFileByEncoding(encodingName,file)) {
//qDebug()<<encodingName;
realEncoding = encodingName;
return;
}
}
}
} else {
realEncoding = encoding;
}
if (realEncoding == ENCODING_SYSTEM_DEFAULT) {
realEncoding = pCharsetInfoManager->getDefaultSystemEncoding();
}
file.reset();
QTextStream textStream(&file);
if (realEncoding == ENCODING_UTF8_BOM) {
textStream.setAutoDetectUnicode(true);
textStream.setCodec(ENCODING_UTF8);
} else {
textStream.setAutoDetectUnicode(false);
textStream.setCodec(realEncoding);
}
QString line;
internalClear();
while (textStream.readLineInto(&line)) {
if (line.endsWith("\r\n")) {
line.remove(line.length()-2,2);
} else if (line.endsWith("\r")) {
line.remove(line.length()-1,1);
} else if (line.endsWith("\n")){
line.remove(line.length()-1,1);
}
addItem(line);
}
}
void SynDocument::saveToFile(QFile &file, const QByteArray& encoding,
const QByteArray& defaultEncoding, QByteArray& realEncoding)
{
QMutexLocker locker(&mMutex);
if (!file.open(QFile::WriteOnly | QFile::Truncate))
throw FileError(tr("Can't open file '%1' for save!").arg(file.fileName()));
if (mLines.isEmpty())
return;
bool allAscii = true;
QTextCodec* codec;
realEncoding = encoding;
if (realEncoding == ENCODING_UTF8_BOM) {
codec = QTextCodec::codecForName(ENCODING_UTF8);
file.putChar(0xEF);
file.putChar(0xBB);
file.putChar(0xBF);
} else if (realEncoding == ENCODING_SYSTEM_DEFAULT) {
codec = QTextCodec::codecForLocale();
} else if (realEncoding == ENCODING_AUTO_DETECT) {
codec = QTextCodec::codecForName(defaultEncoding);
if (!codec)
codec = QTextCodec::codecForLocale();
} else {
codec = QTextCodec::codecForName(realEncoding);
}
for (PSynDocumentLine& line:mLines) {
if (allAscii) {
allAscii = isTextAllAscii(line->fString);
}
if (!allAscii) {
file.write(codec->fromUnicode(line->fString));
} else {
file.write(line->fString.toLatin1());
}
file.write(lineBreak().toLatin1());
}
if (encoding == ENCODING_AUTO_DETECT) {
if (allAscii)
realEncoding = ENCODING_ASCII;
else if (codec->name() == "System") {
realEncoding = pCharsetInfoManager->getDefaultSystemEncoding();
} else {
realEncoding = codec->name();
}
}
}
int SynDocument::stringColumns(const QString &line, int colsBefore) const
{
int columns = std::max(0,colsBefore);
int charCols;
for (int i=0;i<line.length();i++) {
QChar ch = line[i];
if (ch == '\t') {
charCols = mTabWidth - columns % mTabWidth;
} else {
charCols = charColumns(ch);
}
columns+=charCols;
}
return columns-colsBefore;
}
int SynDocument::charColumns(QChar ch) const
{
if (ch.unicode()<=32)
return 1;
//return std::ceil((int)(fontMetrics().horizontalAdvance(ch) * dpiFactor()) / (double)mCharWidth);
return std::ceil((int)(fontMetrics().horizontalAdvance(ch)) / (double)mCharWidth);
}
void SynDocument::putTextStr(const QString &text)
{
beginUpdate();
auto action = finally([this]{
endUpdate();
});
internalClear();
int pos = 0;
int start;
while (pos < text.length()) {
start = pos;
while (pos<text.length()) {
if (text[pos] == '\r' || text[pos] == '\n') {
break;
}
pos++;
}
add(text.mid(start,pos-start));
if (pos>=text.length())
break;
if (text[pos] == '\r')
pos++;
if (text[pos] == '\n')
pos++;
}
}
void SynDocument::internalClear()
{
if (!mLines.isEmpty()) {
beginUpdate();
int oldCount = mLines.count();
mIndexOfLongestLine = -1;
mLines.clear();
emit deleted(0,oldCount);
endUpdate();
}
}
FileEndingType SynDocument::getFileEndingType()
{
QMutexLocker locker(&mMutex);
return mFileEndingType;
}
void SynDocument::setFileEndingType(const FileEndingType &fileEndingType)
{
QMutexLocker locker(&mMutex);
mFileEndingType = fileEndingType;
}
bool SynDocument::empty()
{
QMutexLocker locker(&mMutex);
return mLines.count()==0;
}
void SynDocument::resetColumns()
{
QMutexLocker locker(&mMutex);
mIndexOfLongestLine = -1;
if (mLines.count() > 0 ) {
for (int i=0;i<mLines.size();i++) {
mLines[i]->fColumns = -1;
}
}
}
void SynDocument::invalidAllLineColumns()
{
QMutexLocker locker(&mMutex);
mIndexOfLongestLine = -1;
for (PSynDocumentLine& line:mLines) {
line->fColumns = -1;
}
}
SynDocumentLine::SynDocumentLine():
fString(),
fRange(),
fColumns(-1)
{
}
SynEditUndoList::SynEditUndoList():QObject()
{
mMaxUndoActions = 1024;
mNextChangeNumber = 1;
mInsideRedo = false;
mBlockChangeNumber=0;
mBlockLock=0;
mFullUndoImposible=false;
mBlockCount=0;
mLastPoppedItemChangeNumber=0;
mInitialChangeNumber = 0;
mLastRestoredItemChangeNumber=0;
}
void SynEditUndoList::addChange(SynChangeReason reason, const BufferCoord &startPos,
const BufferCoord &endPos, const QStringList& changeText,
SynSelectionMode selMode)
{
int changeNumber;
if (inBlock()) {
changeNumber = mBlockChangeNumber;
} else {
changeNumber = getNextChangeNumber();
}
PSynEditUndoItem newItem = std::make_shared<SynEditUndoItem>(
reason,
selMode,startPos,endPos,changeText,
changeNumber);
// qDebug()<<"add change"<<changeNumber<<(int)reason;
mItems.append(newItem);
ensureMaxEntries();
if (reason!=SynChangeReason::GroupBreak && !inBlock()) {
mBlockCount++;
// qDebug()<<"add"<<mBlockCount;
emit addedUndo();
}
}
void SynEditUndoList::restoreChange(SynChangeReason AReason, const BufferCoord &AStart, const BufferCoord &AEnd, const QStringList &ChangeText, SynSelectionMode SelMode, size_t changeNumber)
{
PSynEditUndoItem newItem = std::make_shared<SynEditUndoItem>(AReason,
SelMode,AStart,AEnd,ChangeText,
changeNumber);
restoreChange(newItem);
}
void SynEditUndoList::restoreChange(PSynEditUndoItem item)
{
size_t changeNumber = item->changeNumber();
mItems.append(item);
ensureMaxEntries();
if (changeNumber>mNextChangeNumber)
mNextChangeNumber=changeNumber;
if (changeNumber!=mLastRestoredItemChangeNumber) {
// qDebug()<<"restore"<<mBlockCount;
mBlockCount++;
emit addedUndo();
}
mLastRestoredItemChangeNumber=changeNumber;
}
void SynEditUndoList::addGroupBreak()
{
if (!canUndo())
return;
if (lastChangeReason() != SynChangeReason::GroupBreak) {
addChange(SynChangeReason::GroupBreak, {0,0}, {0,0}, QStringList(), SynSelectionMode::Normal);
}
}
void SynEditUndoList::beginBlock()
{
// qDebug()<<"begin block";
if (mBlockLock==0)
mBlockChangeNumber = getNextChangeNumber();
mBlockLock++;
}
void SynEditUndoList::clear()
{
mItems.clear();
mFullUndoImposible = false;
mInitialChangeNumber=0;
mLastPoppedItemChangeNumber=0;
mLastRestoredItemChangeNumber=0;
mBlockCount=0;
mBlockLock=0;
}
void SynEditUndoList::endBlock()
{
// qDebug()<<"end block";
if (mBlockLock > 0) {
mBlockLock--;
if (mBlockLock == 0) {
size_t iBlockID = mBlockChangeNumber;
mBlockChangeNumber = 0;
if (mItems.count() > 0 && peekItem()->changeNumber() == iBlockID) {
mBlockCount++;
// qDebug()<<"end block"<<mBlockCount;
emit addedUndo();
}
}
}
}
bool SynEditUndoList::inBlock()
{
return mBlockLock>0;
}
unsigned int SynEditUndoList::getNextChangeNumber()
{
return mNextChangeNumber++;
}
SynChangeReason SynEditUndoList::lastChangeReason()
{
if (mItems.count() == 0)
return SynChangeReason::Nothing;
else
return mItems.last()->changeReason();
}
bool SynEditUndoList::isEmpty()
{
return mItems.count()==0;
}
PSynEditUndoItem SynEditUndoList::peekItem()
{
if (mItems.count() == 0)
return PSynEditUndoItem();
else
return mItems.last();
}
PSynEditUndoItem SynEditUndoList::popItem()
{
if (mItems.count() == 0)
return PSynEditUndoItem();
else {
PSynEditUndoItem item = mItems.last();
// qDebug()<<"popped"<<item->changeNumber()<<item->changeText()<<(int)item->changeReason()<<mLastPoppedItemChangeNumber;
if (mLastPoppedItemChangeNumber!=item->changeNumber() && item->changeReason()!=SynChangeReason::GroupBreak) {
mBlockCount--;
// qDebug()<<"pop"<<mBlockCount;
if (mBlockCount<0) {
qDebug()<<"block count calculation error";
mBlockCount=0;
}
}
mLastPoppedItemChangeNumber = item->changeNumber();
mItems.removeLast();
return item;
}
}
bool SynEditUndoList::canUndo()
{
return mItems.count()>0;
}
int SynEditUndoList::itemCount()
{
return mItems.count();
}
int SynEditUndoList::maxUndoActions() const
{
return mMaxUndoActions;
}
void SynEditUndoList::setMaxUndoActions(int maxUndoActions)
{
if (maxUndoActions!=mMaxUndoActions) {
mMaxUndoActions = maxUndoActions;
ensureMaxEntries();
}
}
bool SynEditUndoList::initialState()
{
if (itemCount() == 0) {
return mInitialChangeNumber==0;
} else {
return peekItem()->changeNumber() == mInitialChangeNumber;
}
}
void SynEditUndoList::setInitialState()
{
if (itemCount() == 0)
mInitialChangeNumber = 0;
else
mInitialChangeNumber = peekItem()->changeNumber();
}
bool SynEditUndoList::insideRedo() const
{
return mInsideRedo;
}
void SynEditUndoList::setInsideRedo(bool insideRedo)
{
mInsideRedo = insideRedo;
}
bool SynEditUndoList::fullUndoImposible() const
{
return mFullUndoImposible;
}
void SynEditUndoList::ensureMaxEntries()
{
if (mMaxUndoActions>0 && mBlockCount > mMaxUndoActions){
mFullUndoImposible = true;
while (mBlockCount > mMaxUndoActions && !mItems.isEmpty()) {
//remove all undo item in block
PSynEditUndoItem item = mItems.front();
size_t changeNumber = item->changeNumber();
while (mItems.count()>0 && mItems.front()->changeNumber() == changeNumber)
mItems.removeFirst();
if (item->changeReason()!=SynChangeReason::GroupBreak)
mBlockCount--;
}
}
}
SynSelectionMode SynEditUndoItem::changeSelMode() const
{
return mChangeSelMode;
}
BufferCoord SynEditUndoItem::changeStartPos() const
{
return mChangeStartPos;
}
BufferCoord SynEditUndoItem::changeEndPos() const
{
return mChangeEndPos;
}
QStringList SynEditUndoItem::changeText() const
{
return mChangeText;
}
size_t SynEditUndoItem::changeNumber() const
{
return mChangeNumber;
}
SynEditUndoItem::SynEditUndoItem(SynChangeReason reason, SynSelectionMode selMode,
BufferCoord startPos, BufferCoord endPos,
const QStringList& text, int number)
{
mChangeReason = reason;
mChangeSelMode = selMode;
mChangeStartPos = startPos;
mChangeEndPos = endPos;
mChangeText = text;
mChangeNumber = number;
}
SynChangeReason SynEditUndoItem::changeReason() const
{
return mChangeReason;
}
SynEditRedoList::SynEditRedoList()
{
}
void SynEditRedoList::addRedo(SynChangeReason AReason, const BufferCoord &AStart, const BufferCoord &AEnd, const QStringList &ChangeText, SynSelectionMode SelMode, size_t changeNumber)
{
PSynEditUndoItem newItem = std::make_shared<SynEditUndoItem>(
AReason,
SelMode,AStart,AEnd,ChangeText,
changeNumber);
mItems.append(newItem);
}
void SynEditRedoList::addRedo(PSynEditUndoItem item)
{
mItems.append(item);
}
void SynEditRedoList::clear()
{
mItems.clear();
}
SynChangeReason SynEditRedoList::lastChangeReason()
{
if (mItems.count() == 0)
return SynChangeReason::Nothing;
else
return mItems.last()->changeReason();
}
bool SynEditRedoList::isEmpty()
{
return mItems.isEmpty();
}
PSynEditUndoItem SynEditRedoList::peekItem()
{
if (mItems.count() == 0)
return PSynEditUndoItem();
else
return mItems.last();
}
PSynEditUndoItem SynEditRedoList::popItem()
{
if (mItems.count() == 0)
return PSynEditUndoItem();
else {
PSynEditUndoItem item = mItems.last();
mItems.removeLast();
return item;
}
}
bool SynEditRedoList::canRedo()
{
return mItems.count()>0;
}
int SynEditRedoList::itemCount()
{
return mItems.count();
}