1096 lines
27 KiB
C++
1096 lines
27 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>
|
|
|
|
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<mList.size();i++) {
|
|
int len = lineColumns(i);
|
|
if (len > 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<SynEditStringRec>();
|
|
line->fString = s;
|
|
mIndexOfLongestLine = -1;
|
|
mList.insert(Index,line);
|
|
endUpdate();
|
|
}
|
|
|
|
void SynEditStringList::addItem(const QString &s)
|
|
{
|
|
beginUpdate();
|
|
PSynEditStringRec line = std::make_shared<SynEditStringRec>();
|
|
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 <Index+NumLines)) {
|
|
mIndexOfLongestLine = - 1;
|
|
}
|
|
int LinesAfter = mList.count() - (Index + NumLines);
|
|
if (LinesAfter < 0) {
|
|
NumLines = mList.count() - Index;
|
|
}
|
|
mList.remove(Index,NumLines);
|
|
emit deleted(Index,NumLines);
|
|
}
|
|
|
|
void SynEditStringList::exchange(int Index1, int Index2)
|
|
{
|
|
QMutexLocker locker(&mMutex);
|
|
if ((Index1 < 0) || (Index1 >= 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;
|
|
else if (mIndexOfLongestLine>Index)
|
|
mIndexOfLongestLine -= 1;
|
|
mList.removeAt(Index);
|
|
emit deleted(Index,1);
|
|
endUpdate();
|
|
}
|
|
|
|
QString SynEditStringList::getTextStr() const
|
|
{
|
|
QString result;
|
|
for (int i=0;i<mList.count()-1;i++) {
|
|
const PSynEditStringRec& line = mList[i];
|
|
result.append(line->fString);
|
|
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();
|
|
int oldColumns = mList[Index]->fColumns;
|
|
mList[Index]->fString = s;
|
|
calculateLineColumns(Index);
|
|
if (oldColumns>mList[Index]->fColumns)
|
|
mIndexOfLongestLine = -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<Index+NumLines;i++) {
|
|
line = std::make_shared<SynEditStringRec>();
|
|
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<NewStrings.length();i++) {
|
|
line = std::make_shared<SynEditStringRec>();
|
|
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<PCharsetInfo> charsets = pCharsetInfoManager->findCharsetByLocale(pCharsetInfoManager->localeName());
|
|
if (!charsets.isEmpty()) {
|
|
if (tryLoadFileByEncoding(realEncoding,file)) {
|
|
emit inserted(0,mList.count());
|
|
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;
|
|
emit inserted(0,mList.count());
|
|
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);
|
|
}
|
|
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()) {
|
|
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 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;i<mList.size();i++) {
|
|
mList[i]->fColumns = -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<SynEditUndoItem>(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;
|
|
}
|