/* * 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 . */ #include "TextBuffer.h" #include #include #include #include #include #include #include "SynEdit.h" #include "../utils.h" #include "../platform.h" #include SynEditStringList::SynEditStringList(SynEdit *pEdit, QObject *parent): QObject(parent), mEdit(pEdit), mMutex(QMutex::Recursive) { mAppendNewLineAtEOF = true; mFileEndingType = FileEndingType::Windows; mIndexOfLongestLine = -1; mUpdateCount = 0; } static void ListIndexOutOfBounds(int index) { throw IndexOutOfRange(index); } int SynEditStringList::parenthesisLevels(int Index) { QMutexLocker locker(&mMutex); if (Index>=0 && Index < mList.size()) { return mList[Index]->fRange.parenthesisLevel; } else return 0; } int SynEditStringList::bracketLevels(int Index) { QMutexLocker locker(&mMutex); if (Index>=0 && Index < mList.size()) { return mList[Index]->fRange.bracketLevel; } else return 0; } int SynEditStringList::braceLevels(int Index) { QMutexLocker locker(&mMutex); if (Index>=0 && Index < mList.size()) { return mList[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 SynEditStringList::lineColumns(int Index) { QMutexLocker locker(&mMutex); if (Index>=0 && Index < mList.size()) { if (mList[Index]->fColumns == -1) { return calculateLineColumns(Index); } else return mList[Index]->fColumns; } else return 0; } int SynEditStringList::leftBraces(int Index) { QMutexLocker locker(&mMutex); if (Index>=0 && Index < mList.size()) { return mList[Index]->fRange.leftBraces; } else return 0; } int SynEditStringList::rightBraces(int Index) { QMutexLocker locker(&mMutex); if (Index>=0 && Index < mList.size()) { return mList[Index]->fRange.rightBraces; } else return 0; } int SynEditStringList::lengthOfLongestLine() { QMutexLocker locker(&mMutex); if (mIndexOfLongestLine < 0) { int MaxLen = -1; mIndexOfLongestLine = -1; if (mList.count() > 0 ) { for (int i=0;i MaxLen) { MaxLen = len; mIndexOfLongestLine = i; } } } } if (mIndexOfLongestLine >= 0) return mList[mIndexOfLongestLine]->fColumns; else return 0; } QString SynEditStringList::lineBreak() const { switch(mFileEndingType) { case FileEndingType::Linux: return "\n"; case FileEndingType::Windows: return "\r\n"; case FileEndingType::Mac: return "\r"; } return "\n"; } SynRangeState SynEditStringList::ranges(int Index) { QMutexLocker locker(&mMutex); if (Index>=0 && Index < mList.size()) { return mList[Index]->fRange; } else { ListIndexOutOfBounds(Index); } return {0}; } void SynEditStringList::insertItem(int Index, const QString &s) { beginUpdate(); PSynEditStringRec line = std::make_shared(); line->fString = s; mIndexOfLongestLine = -1; mList.insert(Index,line); endUpdate(); } void SynEditStringList::addItem(const QString &s) { beginUpdate(); PSynEditStringRec line = std::make_shared(); line->fString = s; mIndexOfLongestLine = -1; mList.append(line); endUpdate(); } bool SynEditStringList::getAppendNewLineAtEOF() { QMutexLocker locker(&mMutex); return mAppendNewLineAtEOF; } void SynEditStringList::setAppendNewLineAtEOF(bool appendNewLineAtEOF) { QMutexLocker locker(&mMutex); mAppendNewLineAtEOF = appendNewLineAtEOF; } void SynEditStringList::setRange(int Index, const SynRangeState& ARange) { QMutexLocker locker(&mMutex); if (Index<0 || Index>=mList.count()) { ListIndexOutOfBounds(Index); } beginUpdate(); mList[Index]->fRange = ARange; endUpdate(); } QString SynEditStringList::getString(int Index) { QMutexLocker locker(&mMutex); if (Index<0 || Index>=mList.count()) { return QString(); } return mList[Index]->fString; } int SynEditStringList::count() { QMutexLocker locker(&mMutex); return mList.count(); } void *SynEditStringList::getObject(int Index) { QMutexLocker locker(&mMutex); if (Index<0 || Index>=mList.count()) { return nullptr; } return mList[Index]->fObject; } QString SynEditStringList::text() { QMutexLocker locker(&mMutex); return getTextStr(); } void SynEditStringList::setText(const QString &text) { QMutexLocker locker(&mMutex); putTextStr(text); } void SynEditStringList::setContents(const QStringList &text) { QMutexLocker locker(&mMutex); beginUpdate(); auto action = finally([this]{ endUpdate(); }); internalClear(); if (text.count() > 0) { mIndexOfLongestLine = -1; int FirstAdded = mList.count(); foreach (const QString& s,text) { addItem(s); } emit inserted(FirstAdded,text.count()); } } QStringList SynEditStringList::contents() { QMutexLocker locker(&mMutex); QStringList Result; SynEditStringRecList list = mList; foreach (const PSynEditStringRec& line, list) { Result.append(line->fString); } return Result; } void SynEditStringList::beginUpdate() { if (mUpdateCount == 0) { setUpdateState(true); } mUpdateCount++; } void SynEditStringList::endUpdate() { mUpdateCount--; if (mUpdateCount == 0) { setUpdateState(false); } } int SynEditStringList::add(const QString &s) { QMutexLocker locker(&mMutex); beginUpdate(); int Result = mList.count(); insertItem(Result, s); emit inserted(Result,1); endUpdate(); return Result; } void SynEditStringList::addStrings(const QStringList &Strings) { QMutexLocker locker(&mMutex); if (Strings.count() > 0) { mIndexOfLongestLine = -1; beginUpdate(); auto action = finally([this]{ endUpdate(); }); int FirstAdded = mList.count(); for (const QString& s:Strings) { addItem(s); } emit inserted(FirstAdded,Strings.count()); } } int SynEditStringList::getTextLength() { QMutexLocker locker(&mMutex); int Result = 0; foreach (const PSynEditStringRec& line, mList ) { Result += line->fString.length(); if (mFileEndingType == FileEndingType::Windows) { Result += 2; } else { Result += 1; } } return Result; } void SynEditStringList::clear() { QMutexLocker locker(&mMutex); internalClear(); } void SynEditStringList::deleteLines(int Index, int NumLines) { QMutexLocker locker(&mMutex); if (NumLines<=0) return; if ((Index < 0) || (Index >= mList.count())) { ListIndexOutOfBounds(Index); } beginUpdate(); auto action = finally([this]{ endUpdate(); }); if (mIndexOfLongestLine>=Index && (mIndexOfLongestLine = mList.count())) { ListIndexOutOfBounds(Index1); } if ((Index2 < 0) || (Index2 >= mList.count())) { ListIndexOutOfBounds(Index2); } beginUpdate(); PSynEditStringRec temp = mList[Index1]; mList[Index1]=mList[Index2]; mList[Index2]=temp; //mList.swapItemsAt(Index1,Index2); if (mIndexOfLongestLine == Index1) { mIndexOfLongestLine = Index2; } else if (mIndexOfLongestLine == Index2) { mIndexOfLongestLine = Index1; } endUpdate(); } void SynEditStringList::insert(int Index, const QString &s) { QMutexLocker locker(&mMutex); if ((Index < 0) || (Index > mList.count())) { ListIndexOutOfBounds(Index); } beginUpdate(); insertItem(Index, s); emit inserted(Index,1); endUpdate(); } void SynEditStringList::deleteAt(int Index) { QMutexLocker locker(&mMutex); if ((Index < 0) || (Index >= mList.count())) { ListIndexOutOfBounds(Index); } beginUpdate(); if (mIndexOfLongestLine == Index) mIndexOfLongestLine = -1; mList.removeAt(Index); emit deleted(Index,1); endUpdate(); } QString SynEditStringList::getTextStr() const { QString result; for (int i=0;ifString); result.append(lineBreak()); } if (mList.length()>0) { result.append(mList.back()->fString); } return result; } void SynEditStringList::putString(int Index, const QString &s, bool notify) { QMutexLocker locker(&mMutex); if (Index == mList.count()) { add(s); } else { if (Index<0 || Index>=mList.count()) { ListIndexOutOfBounds(Index); } beginUpdate(); mIndexOfLongestLine = -1; mList[Index]->fString = s; mList[Index]->fColumns = -1; if (notify) emit putted(Index,1); endUpdate(); } } void SynEditStringList::putObject(int Index, void *AObject) { QMutexLocker locker(&mMutex); if (Index<0 || Index>=mList.count()) { ListIndexOutOfBounds(Index); } beginUpdate(); mList[Index]->fObject = AObject; endUpdate(); } void SynEditStringList::setUpdateState(bool Updating) { if (Updating) emit changing(); else emit changed(); } int SynEditStringList::calculateLineColumns(int Index) { PSynEditStringRec line = mList[Index]; line->fColumns = mEdit->stringColumns(line->fString,0); return line->fColumns; } void SynEditStringList::insertLines(int Index, int NumLines) { QMutexLocker locker(&mMutex); if (Index<0 || Index>mList.count()) { ListIndexOutOfBounds(Index); } if (NumLines<=0) return; beginUpdate(); auto action = finally([this]{ endUpdate(); }); PSynEditStringRec line; mList.insert(Index,NumLines,line); for (int i=Index;i(); mList[i]=line; } emit inserted(Index,NumLines); } void SynEditStringList::insertStrings(int Index, const QStringList &NewStrings) { QMutexLocker locker(&mMutex); if (Index<0 || Index>mList.count()) { ListIndexOutOfBounds(Index); } if (NewStrings.isEmpty()) return; beginUpdate(); auto action = finally([this]{ endUpdate(); }); PSynEditStringRec line; mList.insert(Index,NewStrings.length(),line); for (int i=0;i(); line->fString = NewStrings[i]; mList[i+Index]=line; } emit inserted(Index,NewStrings.length()); } void SynEditStringList::insertText(int Index, const QString &NewText) { QMutexLocker locker(&mMutex); if (Index<0 || Index>=mList.count()) { ListIndexOutOfBounds(Index); } if (NewText.isEmpty()) return; QStringList lines = textToLines(NewText); insertStrings(Index,lines); } bool SynEditStringList::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; } void SynEditStringList::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(); auto action = finally([this]{ endUpdate(); }); //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(); } emit inserted(0,mList.count()); if (!needReread) { if (allAscii) realEncoding = ENCODING_ASCII; return; } realEncoding = pCharsetInfoManager->getDefaultSystemEncoding(); QList charsets = pCharsetInfoManager->findCharsetByLocale(pCharsetInfoManager->localeName()); if (!charsets.isEmpty()) { if (tryLoadFileByEncoding(realEncoding,file)) { emit inserted(0,mList.count()); return; } QSet encodingSet; for (int i=0;iname); } encodingSet.remove(realEncoding); foreach (const QByteArray& encodingName,encodingSet) { if (encodingName == ENCODING_UTF8) continue; if (tryLoadFileByEncoding(encodingName,file)) { qDebug()<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); } emit inserted(0,mList.count()); } void SynEditStringList::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 (mList.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 (PSynEditStringRec& line:mList) { 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(); } } } void SynEditStringList::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()) break; if (text[pos] == '\r') pos++; if (text[pos] == '\n') pos++; } } void SynEditStringList::internalClear() { if (!mList.isEmpty()) { beginUpdate(); int oldCount = mList.count(); mIndexOfLongestLine = -1; mList.clear(); emit deleted(0,oldCount); endUpdate(); } } FileEndingType SynEditStringList::getFileEndingType() { QMutexLocker locker(&mMutex); return mFileEndingType; } void SynEditStringList::setFileEndingType(const FileEndingType &fileEndingType) { QMutexLocker locker(&mMutex); mFileEndingType = fileEndingType; } bool SynEditStringList::empty() { QMutexLocker locker(&mMutex); return mList.count()==0; } void SynEditStringList::resetColumns() { QMutexLocker locker(&mMutex); mIndexOfLongestLine = -1; if (mList.count() > 0 ) { for (int i=0;ifColumns = -1; } } } void SynEditStringList::invalidAllLineColumns() { QMutexLocker locker(&mMutex); mIndexOfLongestLine = -1; for (PSynEditStringRec& line:mList) { line->fColumns = -1; } } SynEditStringRec::SynEditStringRec(): fString(), fObject(nullptr), fRange{0,0,0,0,0}, fColumns(-1) { } SynEditUndoList::SynEditUndoList():QObject() { mMaxUndoActions = 1024; mNextChangeNumber = 1; mInsideRedo = false; mBlockChangeNumber=0; mBlockCount=0; mFullUndoImposible=false; mLockCount = 0; mInitialChangeNumber = 0; } void SynEditUndoList::AddChange(SynChangeReason AReason, const BufferCoord &AStart, const BufferCoord &AEnd, const QString &ChangeText, SynSelectionMode SelMode) { if (mLockCount != 0) return; int changeNumber; if (mBlockChangeNumber != 0) { changeNumber = mBlockChangeNumber; } else { changeNumber = mNextChangeNumber; if (mBlockCount == 0) { mNextChangeNumber++; if (mNextChangeNumber == 0) { mNextChangeNumber++; } } } PSynEditUndoItem NewItem = std::make_shared(AReason, SelMode,AStart,AEnd,ChangeText, changeNumber); PushItem(NewItem); } void SynEditUndoList::AddGroupBreak() { //Add the GroupBreak even if ItemCount = 0. Since items are stored in //reverse order in TCustomSynEdit.fRedoList, a GroupBreak could be lost. if (LastChangeReason() != SynChangeReason::crGroupBreak) { AddChange(SynChangeReason::crGroupBreak, {0,0}, {0,0}, "", SynSelectionMode::smNormal); } } void SynEditUndoList::BeginBlock() { mBlockCount++; mBlockChangeNumber = mNextChangeNumber; } void SynEditUndoList::Clear() { mItems.clear(); mFullUndoImposible = false; } void SynEditUndoList::DeleteItem(int index) { if (index <0 || index>=mItems.count()) { ListIndexOutOfBounds(index); } mItems.removeAt(index); } void SynEditUndoList::EndBlock() { if (mBlockCount > 0) { mBlockCount--; if (mBlockCount == 0) { int iBlockID = mBlockChangeNumber; mBlockChangeNumber = 0; mNextChangeNumber++; if (mNextChangeNumber == 0) mNextChangeNumber++; if (mItems.count() > 0 && PeekItem()->changeNumber() == iBlockID) emit addedUndo(); } } } SynChangeReason SynEditUndoList::LastChangeReason() { if (mItems.count() == 0) return SynChangeReason::crNothing; else return mItems.last()->changeReason(); } bool SynEditUndoList::isEmpty() { return mItems.count()==0; } void SynEditUndoList::Lock() { mLockCount++; } 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(); mItems.removeLast(); return item; } } void SynEditUndoList::PushItem(PSynEditUndoItem Item) { if (!Item) return; mItems.append(Item); ensureMaxEntries(); if (Item->changeReason()!= SynChangeReason::crGroupBreak) emit addedUndo(); } void SynEditUndoList::Unlock() { if (mLockCount > 0) mLockCount--; } 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; } } PSynEditUndoItem SynEditUndoList::item(int index) { if (index <0 || index>=mItems.count()) { ListIndexOutOfBounds(index); } return mItems[index]; } void SynEditUndoList::setInitialState(const bool Value) { if (Value) { if (ItemCount() == 0) mInitialChangeNumber = 0; else mInitialChangeNumber = PeekItem()->changeNumber(); } else if (ItemCount() == 0) { if (mInitialChangeNumber == 0) { mInitialChangeNumber = -1; } } else if (PeekItem()->changeNumber() == mInitialChangeNumber) { mInitialChangeNumber = -1; } } void SynEditUndoList::setItem(int index, PSynEditUndoItem Value) { if (index <0 || index>=mItems.count()) { ListIndexOutOfBounds(index); } mItems[index]=Value; } int SynEditUndoList::blockChangeNumber() const { return mBlockChangeNumber; } void SynEditUndoList::setBlockChangeNumber(int blockChangeNumber) { mBlockChangeNumber = blockChangeNumber; } int SynEditUndoList::blockCount() const { return mBlockCount; } bool SynEditUndoList::insideRedo() const { return mInsideRedo; } void SynEditUndoList::setInsideRedo(bool insideRedo) { mInsideRedo = insideRedo; } bool SynEditUndoList::fullUndoImposible() const { return mFullUndoImposible; } void SynEditUndoList::ensureMaxEntries() { if (mItems.count() > mMaxUndoActions){ mFullUndoImposible = true; while (mItems.count() > mMaxUndoActions) { //remove all undo item in block int changeNumber = mItems.front()->changeNumber(); while (mItems.count()>0 && mItems.front()->changeNumber() == changeNumber) mItems.removeFirst(); } } } SynSelectionMode SynEditUndoItem::changeSelMode() const { return mChangeSelMode; } BufferCoord SynEditUndoItem::changeStartPos() const { return mChangeStartPos; } BufferCoord SynEditUndoItem::changeEndPos() const { return mChangeEndPos; } QString SynEditUndoItem::changeStr() const { return mChangeStr; } int SynEditUndoItem::changeNumber() const { return mChangeNumber; } SynEditUndoItem::SynEditUndoItem(SynChangeReason reason, SynSelectionMode selMode, BufferCoord startPos, BufferCoord endPos, const QString &str, int number) { mChangeReason = reason; mChangeSelMode = selMode; mChangeStartPos = startPos; mChangeEndPos = endPos; mChangeStr = str; mChangeNumber = number; } SynChangeReason SynEditUndoItem::changeReason() const { return mChangeReason; }