2009-02-25 09:15:00 +01:00
|
|
|
/**************************************************************************
|
2008-12-02 12:01:29 +01:00
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator
|
|
|
|
|
**
|
2011-01-11 16:28:15 +01:00
|
|
|
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
2008-12-02 12:01:29 +01:00
|
|
|
**
|
2009-06-17 00:01:27 +10:00
|
|
|
** Contact: Nokia Corporation (qt-info@nokia.com)
|
2008-12-02 12:01:29 +01:00
|
|
|
**
|
2010-12-17 16:01:08 +01:00
|
|
|
** No Commercial Usage
|
2008-12-02 14:17:16 +01:00
|
|
|
**
|
2010-12-17 16:01:08 +01:00
|
|
|
** This file contains pre-release code and may not be distributed.
|
|
|
|
|
** You may use this file in accordance with the terms and conditions
|
|
|
|
|
** contained in the Technology Preview License Agreement accompanying
|
|
|
|
|
** this package.
|
2008-12-02 14:17:16 +01:00
|
|
|
**
|
2009-02-25 09:15:00 +01:00
|
|
|
** GNU Lesser General Public License Usage
|
2008-12-02 14:17:16 +01:00
|
|
|
**
|
2009-02-25 09:15:00 +01:00
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
|
|
|
** General Public License version 2.1 as published by the Free Software
|
|
|
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
|
|
|
** packaging of this file. Please review the following information to
|
|
|
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
|
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
2008-12-02 14:17:16 +01:00
|
|
|
**
|
2010-12-17 16:01:08 +01:00
|
|
|
** In addition, as a special exception, Nokia gives you certain additional
|
|
|
|
|
** rights. These rights are described in the Nokia Qt LGPL Exception
|
|
|
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
|
|
|
**
|
|
|
|
|
** If you have questions regarding the use of this file, please contact
|
|
|
|
|
** Nokia at qt-info@nokia.com.
|
2008-12-02 12:01:29 +01:00
|
|
|
**
|
2009-02-25 09:15:00 +01:00
|
|
|
**************************************************************************/
|
2008-12-02 16:19:05 +01:00
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
#include "basetextdocument.h"
|
2010-04-26 14:02:09 +02:00
|
|
|
|
|
|
|
|
#include "basetextdocumentlayout.h"
|
2008-12-02 12:01:29 +01:00
|
|
|
#include "basetexteditor.h"
|
|
|
|
|
#include "storagesettings.h"
|
2010-10-29 15:20:10 +02:00
|
|
|
#include "tabsettings.h"
|
2011-02-01 14:13:54 +01:00
|
|
|
#include "extraencodingsettings.h"
|
2010-07-09 14:47:18 +02:00
|
|
|
#include "syntaxhighlighter.h"
|
2011-02-04 14:55:50 +01:00
|
|
|
#include "texteditorconstants.h"
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2011-02-04 13:12:58 +01:00
|
|
|
#include <QtCore/QStringList>
|
2008-12-02 12:01:29 +01:00
|
|
|
#include <QtCore/QFile>
|
2010-04-01 14:27:52 +02:00
|
|
|
#include <QtCore/QDir>
|
2008-12-02 12:01:29 +01:00
|
|
|
#include <QtCore/QFileInfo>
|
|
|
|
|
#include <QtCore/QTextStream>
|
|
|
|
|
#include <QtCore/QTextCodec>
|
2011-02-04 14:55:50 +01:00
|
|
|
#include <QtCore/QFutureInterface>
|
2008-12-02 12:01:29 +01:00
|
|
|
#include <QtGui/QMainWindow>
|
|
|
|
|
#include <QtGui/QSyntaxHighlighter>
|
|
|
|
|
#include <QtGui/QApplication>
|
|
|
|
|
|
2009-07-13 15:00:20 +02:00
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
2010-02-04 10:33:12 +01:00
|
|
|
#include <coreplugin/icore.h>
|
2011-02-04 14:55:50 +01:00
|
|
|
#include <coreplugin/progressmanager/progressmanager.h>
|
2008-12-09 15:25:01 +01:00
|
|
|
#include <utils/qtcassert.h>
|
2010-02-04 10:33:12 +01:00
|
|
|
#include <utils/reloadpromptutils.h>
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2011-02-04 13:12:58 +01:00
|
|
|
namespace {
|
|
|
|
|
bool verifyDecodingError(const QString &text,
|
|
|
|
|
QTextCodec *codec,
|
|
|
|
|
const char *data,
|
|
|
|
|
const int dataSize,
|
|
|
|
|
const bool possibleHeader)
|
|
|
|
|
{
|
|
|
|
|
QByteArray verifyBuf = codec->fromUnicode(text); // slow
|
|
|
|
|
// the minSize trick lets us ignore unicode headers
|
|
|
|
|
int minSize = qMin(verifyBuf.size(), dataSize);
|
|
|
|
|
return (minSize < dataSize - (possibleHeader? 4 : 0)
|
|
|
|
|
|| memcmp(verifyBuf.constData() + verifyBuf.size() - minSize,
|
|
|
|
|
data + dataSize - minSize,
|
|
|
|
|
minSize));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
namespace TextEditor {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
class DocumentMarker : public ITextMarkable
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
public:
|
|
|
|
|
DocumentMarker(QTextDocument *);
|
|
|
|
|
|
|
|
|
|
// ITextMarkable
|
|
|
|
|
bool addMark(ITextMark *mark, int line);
|
|
|
|
|
TextMarks marksAt(int line) const;
|
|
|
|
|
void removeMark(ITextMark *mark);
|
|
|
|
|
bool hasMark(ITextMark *mark) const;
|
|
|
|
|
void updateMark(ITextMark *mark);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QTextDocument *document;
|
|
|
|
|
};
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
DocumentMarker::DocumentMarker(QTextDocument *doc)
|
|
|
|
|
: ITextMarkable(doc), document(doc)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2010-04-26 14:02:09 +02:00
|
|
|
bool DocumentMarker::addMark(TextEditor::ITextMark *mark, int line)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(line >= 1, return false);
|
|
|
|
|
int blockNumber = line - 1;
|
2010-04-26 14:06:29 +02:00
|
|
|
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document->documentLayout());
|
2010-04-26 14:02:09 +02:00
|
|
|
QTC_ASSERT(documentLayout, return false);
|
|
|
|
|
QTextBlock block = document->findBlockByNumber(blockNumber);
|
|
|
|
|
|
|
|
|
|
if (block.isValid()) {
|
2010-04-26 14:06:29 +02:00
|
|
|
TextBlockUserData *userData = BaseTextDocumentLayout::userData(block);
|
2010-04-26 14:02:09 +02:00
|
|
|
userData->addMark(mark);
|
|
|
|
|
mark->updateLineNumber(blockNumber + 1);
|
|
|
|
|
mark->updateBlock(block);
|
|
|
|
|
documentLayout->hasMarks = true;
|
|
|
|
|
documentLayout->requestUpdate();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextEditor::TextMarks DocumentMarker::marksAt(int line) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(line >= 1, return TextMarks());
|
|
|
|
|
int blockNumber = line - 1;
|
|
|
|
|
QTextBlock block = document->findBlockByNumber(blockNumber);
|
|
|
|
|
|
|
|
|
|
if (block.isValid()) {
|
2010-04-26 14:06:29 +02:00
|
|
|
if (TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block))
|
2010-04-26 14:02:09 +02:00
|
|
|
return userData->marks();
|
|
|
|
|
}
|
|
|
|
|
return TextMarks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DocumentMarker::removeMark(TextEditor::ITextMark *mark)
|
|
|
|
|
{
|
|
|
|
|
bool needUpdate = false;
|
|
|
|
|
QTextBlock block = document->begin();
|
|
|
|
|
while (block.isValid()) {
|
|
|
|
|
if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) {
|
|
|
|
|
needUpdate |= data->removeMark(mark);
|
|
|
|
|
}
|
|
|
|
|
block = block.next();
|
|
|
|
|
}
|
|
|
|
|
if (needUpdate)
|
|
|
|
|
updateMark(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DocumentMarker::hasMark(TextEditor::ITextMark *mark) const
|
|
|
|
|
{
|
|
|
|
|
QTextBlock block = document->begin();
|
|
|
|
|
while (block.isValid()) {
|
|
|
|
|
if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) {
|
|
|
|
|
if (data->hasMark(mark))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
block = block.next();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DocumentMarker::updateMark(ITextMark *mark)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(mark)
|
2010-04-26 14:06:29 +02:00
|
|
|
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document->documentLayout());
|
2010-04-26 14:02:09 +02:00
|
|
|
QTC_ASSERT(documentLayout, return);
|
|
|
|
|
documentLayout->requestUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
|
2010-10-29 15:36:49 +02:00
|
|
|
class BaseTextDocumentPrivate
|
|
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
public:
|
|
|
|
|
explicit BaseTextDocumentPrivate(BaseTextDocument *q);
|
|
|
|
|
|
|
|
|
|
QString m_fileName;
|
|
|
|
|
QString m_defaultPath;
|
|
|
|
|
QString m_suggestedFileName;
|
|
|
|
|
QString m_mimeType;
|
|
|
|
|
StorageSettings m_storageSettings;
|
|
|
|
|
TabSettings m_tabSettings;
|
2011-02-01 14:13:54 +01:00
|
|
|
ExtraEncodingSettings m_extraEncodingSettings;
|
2010-10-29 15:20:10 +02:00
|
|
|
QTextDocument *m_document;
|
|
|
|
|
Internal::DocumentMarker *m_documentMarker;
|
|
|
|
|
SyntaxHighlighter *m_highlighter;
|
|
|
|
|
|
|
|
|
|
enum LineTerminatorMode {
|
|
|
|
|
LFLineTerminator,
|
|
|
|
|
CRLFLineTerminator,
|
|
|
|
|
NativeLineTerminator =
|
|
|
|
|
#if defined (Q_OS_WIN)
|
|
|
|
|
CRLFLineTerminator
|
|
|
|
|
#else
|
|
|
|
|
LFLineTerminator
|
|
|
|
|
#endif
|
|
|
|
|
};
|
|
|
|
|
LineTerminatorMode m_lineTerminatorMode;
|
|
|
|
|
QTextCodec *m_codec;
|
|
|
|
|
bool m_fileHasUtf8Bom;
|
|
|
|
|
|
|
|
|
|
bool m_fileIsReadOnly;
|
|
|
|
|
bool m_hasDecodingError;
|
|
|
|
|
QByteArray m_decodingErrorSample;
|
2011-02-07 08:40:24 +01:00
|
|
|
static const int kChunkSize;
|
2010-10-29 15:20:10 +02:00
|
|
|
};
|
|
|
|
|
|
2011-02-07 08:40:24 +01:00
|
|
|
const int BaseTextDocumentPrivate::kChunkSize = 65536;
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
BaseTextDocumentPrivate::BaseTextDocumentPrivate(BaseTextDocument *q) :
|
|
|
|
|
m_document(new QTextDocument(q)),
|
|
|
|
|
m_documentMarker(new Internal::DocumentMarker(m_document)),
|
|
|
|
|
m_highlighter(0),
|
|
|
|
|
m_lineTerminatorMode(NativeLineTerminator),
|
2011-02-01 14:13:54 +01:00
|
|
|
m_codec(Core::EditorManager::instance()->defaultTextCodec()),
|
2010-10-29 15:20:10 +02:00
|
|
|
m_fileHasUtf8Bom(false),
|
|
|
|
|
m_fileIsReadOnly(false),
|
|
|
|
|
m_hasDecodingError(false)
|
|
|
|
|
{
|
|
|
|
|
}
|
2010-04-26 14:02:09 +02:00
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
BaseTextDocument::BaseTextDocument() : d(new BaseTextDocumentPrivate(this))
|
2008-12-02 12:01:29 +01:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BaseTextDocument::~BaseTextDocument()
|
|
|
|
|
{
|
2010-05-28 14:06:57 +02:00
|
|
|
documentClosing();
|
2010-10-29 15:20:10 +02:00
|
|
|
delete d->m_document;
|
|
|
|
|
d->m_document = 0;
|
|
|
|
|
delete d;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString BaseTextDocument::mimeType() const
|
|
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
return d->m_mimeType;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::setMimeType(const QString &mt)
|
|
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_mimeType = mt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::setStorageSettings(const StorageSettings &storageSettings)
|
|
|
|
|
{
|
|
|
|
|
d->m_storageSettings = storageSettings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const StorageSettings &BaseTextDocument::storageSettings() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_storageSettings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::setTabSettings(const TabSettings &tabSettings)
|
|
|
|
|
{
|
|
|
|
|
d->m_tabSettings = tabSettings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TabSettings &BaseTextDocument::tabSettings() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_tabSettings;
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-01 14:13:54 +01:00
|
|
|
void BaseTextDocument::setExtraEncodingSettings(const ExtraEncodingSettings &extraEncodingSettings)
|
|
|
|
|
{
|
|
|
|
|
d->m_extraEncodingSettings = extraEncodingSettings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ExtraEncodingSettings &BaseTextDocument::extraEncodingSettings() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_extraEncodingSettings;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
QString BaseTextDocument::fileName() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_fileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BaseTextDocument::isSaveAsAllowed() const
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString BaseTextDocument::defaultPath() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_defaultPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString BaseTextDocument::suggestedFileName() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_suggestedFileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::setDefaultPath(const QString &defaultPath)
|
|
|
|
|
{
|
|
|
|
|
d->m_defaultPath = defaultPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::setSuggestedFileName(const QString &suggestedFileName)
|
|
|
|
|
{
|
|
|
|
|
d->m_suggestedFileName = suggestedFileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTextDocument *BaseTextDocument::document() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_document;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SyntaxHighlighter *BaseTextDocument::syntaxHighlighter() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_highlighter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BaseTextDocument::hasDecodingError() const
|
|
|
|
|
{
|
2011-02-04 15:00:43 +01:00
|
|
|
return d->m_hasDecodingError;
|
2010-10-29 15:20:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTextCodec *BaseTextDocument::codec() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_codec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::setCodec(QTextCodec *c)
|
|
|
|
|
{
|
|
|
|
|
d->m_codec = c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray BaseTextDocument::decodingErrorSample() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_decodingErrorSample;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ITextMarkable *BaseTextDocument::documentMarker() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_documentMarker;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BaseTextDocument::save(const QString &fileName)
|
|
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
QTextCursor cursor(d->m_document);
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2010-07-07 13:17:58 +02:00
|
|
|
// When saving the current editor, make sure to maintain the cursor position for undo
|
|
|
|
|
Core::IEditor *currentEditor = Core::EditorManager::instance()->currentEditor();
|
2011-02-21 16:02:26 +01:00
|
|
|
if (BaseTextEditor *editable = qobject_cast<BaseTextEditor*>(currentEditor)) {
|
2010-10-29 15:20:10 +02:00
|
|
|
if (editable->file() == this)
|
2011-02-21 16:02:26 +01:00
|
|
|
cursor.setPosition(editable->editorWidget()->textCursor().position());
|
2010-07-07 13:17:58 +02:00
|
|
|
}
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
cursor.beginEditBlock();
|
2010-07-07 13:17:58 +02:00
|
|
|
cursor.movePosition(QTextCursor::Start);
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
if (d->m_storageSettings.m_cleanWhitespace)
|
|
|
|
|
cleanWhitespace(cursor, d->m_storageSettings.m_cleanIndentation, d->m_storageSettings.m_inEntireDocument);
|
|
|
|
|
if (d->m_storageSettings.m_addFinalNewLine)
|
2008-12-02 12:01:29 +01:00
|
|
|
ensureFinalNewLine(cursor);
|
|
|
|
|
cursor.endEditBlock();
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
QString fName = d->m_fileName;
|
2008-12-02 12:01:29 +01:00
|
|
|
if (!fileName.isEmpty())
|
|
|
|
|
fName = fileName;
|
|
|
|
|
|
|
|
|
|
QFile file(fName);
|
|
|
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
|
|
|
return false;
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
QString plainText = d->m_document->toPlainText();
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
if (d->m_lineTerminatorMode == BaseTextDocumentPrivate::CRLFLineTerminator)
|
2008-12-02 12:01:29 +01:00
|
|
|
plainText.replace(QLatin1Char('\n'), QLatin1String("\r\n"));
|
|
|
|
|
|
2011-02-01 14:13:54 +01:00
|
|
|
if (d->m_codec->name() == "UTF-8"
|
|
|
|
|
&& (d->m_extraEncodingSettings.m_utf8BomSetting == ExtraEncodingSettings::AlwaysAdd
|
|
|
|
|
|| (d->m_extraEncodingSettings.m_utf8BomSetting == ExtraEncodingSettings::OnlyKeep
|
|
|
|
|
&& d->m_fileHasUtf8Bom))) {
|
2010-10-28 19:47:49 +02:00
|
|
|
file.write("\xef\xbb\xbf", 3);
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
file.write(d->m_codec->fromUnicode(plainText));
|
2008-12-02 12:01:29 +01:00
|
|
|
if (!file.flush())
|
|
|
|
|
return false;
|
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
|
|
const QFileInfo fi(fName);
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_fileName = QDir::cleanPath(fi.absoluteFilePath());
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_document->setModified(false);
|
2008-12-02 12:01:29 +01:00
|
|
|
emit titleChanged(fi.fileName());
|
|
|
|
|
emit changed();
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_hasDecodingError = false;
|
|
|
|
|
d->m_decodingErrorSample.clear();
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2010-05-11 14:13:38 +02:00
|
|
|
void BaseTextDocument::rename(const QString &newName)
|
|
|
|
|
{
|
|
|
|
|
const QFileInfo fi(newName);
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_fileName = QDir::cleanPath(fi.absoluteFilePath());
|
2010-05-11 14:13:38 +02:00
|
|
|
emit titleChanged(fi.fileName());
|
|
|
|
|
emit changed();
|
|
|
|
|
}
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
bool BaseTextDocument::isReadOnly() const
|
|
|
|
|
{
|
2011-02-04 15:00:43 +01:00
|
|
|
if (d->m_hasDecodingError)
|
2008-12-02 12:01:29 +01:00
|
|
|
return true;
|
2010-10-29 15:20:10 +02:00
|
|
|
if (d->m_fileName.isEmpty()) //have no corresponding file, so editing is ok
|
2008-12-02 12:01:29 +01:00
|
|
|
return false;
|
2010-10-29 15:20:10 +02:00
|
|
|
return d->m_fileIsReadOnly;
|
2009-05-08 15:48:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BaseTextDocument::isModified() const
|
|
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
return d->m_document->isModified();
|
2009-05-08 15:48:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::checkPermissions()
|
|
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
bool previousReadOnly = d->m_fileIsReadOnly;
|
|
|
|
|
if (!d->m_fileName.isEmpty()) {
|
|
|
|
|
const QFileInfo fi(d->m_fileName);
|
|
|
|
|
d->m_fileIsReadOnly = !fi.isWritable();
|
2009-05-08 15:48:00 +02:00
|
|
|
} else {
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_fileIsReadOnly = false;
|
2009-05-08 15:48:00 +02:00
|
|
|
}
|
2010-10-29 15:20:10 +02:00
|
|
|
if (previousReadOnly != d->m_fileIsReadOnly)
|
2010-04-08 16:07:23 +02:00
|
|
|
emit changed();
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BaseTextDocument::open(const QString &fileName)
|
|
|
|
|
{
|
|
|
|
|
QString title = tr("untitled");
|
|
|
|
|
if (!fileName.isEmpty()) {
|
|
|
|
|
const QFileInfo fi(fileName);
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_fileIsReadOnly = !fi.isWritable();
|
|
|
|
|
d->m_fileName = QDir::cleanPath(fi.absoluteFilePath());
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
QFile file(fileName);
|
2009-04-15 16:06:49 +02:00
|
|
|
if (!file.open(QIODevice::ReadOnly))
|
2009-04-09 15:54:30 +02:00
|
|
|
return false;
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
title = fi.fileName();
|
|
|
|
|
|
2011-02-22 16:26:45 +01:00
|
|
|
QByteArray buf;
|
|
|
|
|
try {
|
|
|
|
|
buf = file.readAll();
|
|
|
|
|
} catch (std::bad_alloc) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-12-02 12:01:29 +01:00
|
|
|
int bytesRead = buf.size();
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
QTextCodec *codec = d->m_codec;
|
|
|
|
|
d->m_fileHasUtf8Bom = false;
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
// code taken from qtextstream
|
|
|
|
|
if (bytesRead >= 4 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe && uchar(buf[2]) == 0 && uchar(buf[3]) == 0)
|
|
|
|
|
|| (uchar(buf[0]) == 0 && uchar(buf[1]) == 0 && uchar(buf[2]) == 0xfe && uchar(buf[3]) == 0xff))) {
|
|
|
|
|
codec = QTextCodec::codecForName("UTF-32");
|
|
|
|
|
} else if (bytesRead >= 2 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe)
|
|
|
|
|
|| (uchar(buf[0]) == 0xfe && uchar(buf[1]) == 0xff))) {
|
|
|
|
|
codec = QTextCodec::codecForName("UTF-16");
|
2010-10-28 19:47:49 +02:00
|
|
|
} else if (bytesRead >= 3 && ((uchar(buf[0]) == 0xef && uchar(buf[1]) == 0xbb) && uchar(buf[2]) == 0xbf)) {
|
|
|
|
|
codec = QTextCodec::codecForName("UTF-8");
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_fileHasUtf8Bom = true;
|
2008-12-02 12:01:29 +01:00
|
|
|
} else if (!codec) {
|
|
|
|
|
codec = QTextCodec::codecForLocale();
|
|
|
|
|
}
|
|
|
|
|
// end code taken from qtextstream
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_codec = codec;
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2011-02-04 13:12:58 +01:00
|
|
|
// An alternative to the code below would be creating a decoder from the codec,
|
|
|
|
|
// but failure detection doesn't seem be working reliably.
|
|
|
|
|
QStringList content;
|
|
|
|
|
if (bytesRead <= BaseTextDocumentPrivate::kChunkSize) {
|
|
|
|
|
QString text = d->m_codec->toUnicode(buf);
|
|
|
|
|
d->m_hasDecodingError = verifyDecodingError(
|
|
|
|
|
text, d->m_codec, buf.constData(), bytesRead, true);
|
|
|
|
|
content.append(text);
|
|
|
|
|
} else {
|
|
|
|
|
// Avoid large allocation of contiguous memory.
|
|
|
|
|
QTextCodec::ConverterState state;
|
|
|
|
|
int offset = 0;
|
|
|
|
|
while (offset < bytesRead) {
|
|
|
|
|
int currentSize = qMin(BaseTextDocumentPrivate::kChunkSize, bytesRead - offset);
|
|
|
|
|
QString text = d->m_codec->toUnicode(buf.constData() + offset, currentSize, &state);
|
|
|
|
|
if (state.remainingChars) {
|
|
|
|
|
if (currentSize < BaseTextDocumentPrivate::kChunkSize && !d->m_hasDecodingError)
|
|
|
|
|
d->m_hasDecodingError = true;
|
|
|
|
|
|
|
|
|
|
// Process until the end of the current multi-byte character. Remaining might
|
|
|
|
|
// actually contain more than needed so try one-be-one.
|
|
|
|
|
while (state.remainingChars) {
|
|
|
|
|
text.append(d->m_codec->toUnicode(
|
|
|
|
|
buf.constData() + offset + currentSize, 1, &state));
|
|
|
|
|
++currentSize;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!d->m_hasDecodingError) {
|
|
|
|
|
d->m_hasDecodingError = verifyDecodingError(
|
|
|
|
|
text, d->m_codec, buf.constData() + offset, currentSize, offset == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
offset += currentSize;
|
|
|
|
|
content.append(text);
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
if (d->m_hasDecodingError) {
|
2008-12-02 12:01:29 +01:00
|
|
|
int p = buf.indexOf('\n', 16384);
|
|
|
|
|
if (p < 0)
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_decodingErrorSample = buf;
|
2008-12-02 12:01:29 +01:00
|
|
|
else
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_decodingErrorSample = buf.left(p);
|
2008-12-02 12:01:29 +01:00
|
|
|
} else {
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_decodingErrorSample.clear();
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
2011-02-07 08:48:10 +01:00
|
|
|
buf.clear();
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2011-02-04 13:12:58 +01:00
|
|
|
foreach (const QString &text, content) {
|
|
|
|
|
int lf = text.indexOf('\n');
|
|
|
|
|
if (lf >= 0) {
|
|
|
|
|
if (lf > 0 && text.at(lf-1) == QLatin1Char('\r')) {
|
|
|
|
|
d->m_lineTerminatorMode = BaseTextDocumentPrivate::CRLFLineTerminator;
|
|
|
|
|
} else {
|
|
|
|
|
d->m_lineTerminatorMode = BaseTextDocumentPrivate::LFLineTerminator;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_document->setModified(false);
|
2011-02-04 15:00:43 +01:00
|
|
|
const int chunks = content.size();
|
|
|
|
|
if (chunks == 1) {
|
|
|
|
|
d->m_document->setPlainText(content.at(0));
|
2011-02-04 13:12:58 +01:00
|
|
|
} else {
|
2011-02-04 15:00:43 +01:00
|
|
|
QFutureInterface<void> interface;
|
|
|
|
|
interface.setProgressRange(0, chunks);
|
|
|
|
|
Core::ICore::instance()->progressManager()->addTask(
|
|
|
|
|
interface.future(), tr("Opening file"), Constants::TASK_OPEN_FILE);
|
|
|
|
|
interface.reportStarted();
|
2011-02-08 14:00:10 +01:00
|
|
|
d->m_document->setUndoRedoEnabled(false);
|
2011-02-04 15:00:43 +01:00
|
|
|
QTextCursor c(d->m_document);
|
2011-02-10 15:03:38 +01:00
|
|
|
c.beginEditBlock();
|
|
|
|
|
d->m_document->clear();
|
2011-02-04 15:00:43 +01:00
|
|
|
for (int i = 0; i < chunks; ++i) {
|
|
|
|
|
c.insertText(content.at(i));
|
|
|
|
|
interface.setProgressValue(i + 1);
|
|
|
|
|
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
2011-02-04 13:12:58 +01:00
|
|
|
}
|
2011-02-10 15:03:38 +01:00
|
|
|
c.endEditBlock();
|
2011-02-08 14:00:10 +01:00
|
|
|
d->m_document->setUndoRedoEnabled(true);
|
2011-02-04 15:00:43 +01:00
|
|
|
interface.reportFinished();
|
2011-02-04 13:12:58 +01:00
|
|
|
}
|
|
|
|
|
BaseTextDocumentLayout *documentLayout =
|
|
|
|
|
qobject_cast<BaseTextDocumentLayout*>(d->m_document->documentLayout());
|
2008-12-09 15:25:01 +01:00
|
|
|
QTC_ASSERT(documentLayout, return true);
|
2010-10-29 15:20:10 +02:00
|
|
|
documentLayout->lastSaveRevision = d->m_document->revision();
|
|
|
|
|
d->m_document->setModified(false);
|
2008-12-02 12:01:29 +01:00
|
|
|
emit titleChanged(title);
|
|
|
|
|
emit changed();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::reload(QTextCodec *codec)
|
|
|
|
|
{
|
2008-12-09 15:25:01 +01:00
|
|
|
QTC_ASSERT(codec, return);
|
2010-10-29 15:20:10 +02:00
|
|
|
d->m_codec = codec;
|
2008-12-02 12:01:29 +01:00
|
|
|
reload();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::reload()
|
|
|
|
|
{
|
|
|
|
|
emit aboutToReload();
|
2010-05-28 14:06:57 +02:00
|
|
|
documentClosing(); // removes text marks non-permanently
|
|
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
if (open(d->m_fileName))
|
2008-12-02 12:01:29 +01:00
|
|
|
emit reloaded();
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-19 10:28:05 +01:00
|
|
|
Core::IFile::ReloadBehavior BaseTextDocument::reloadBehavior(ChangeTrigger state, ChangeType type) const
|
2008-12-02 12:01:29 +01:00
|
|
|
{
|
2010-03-19 10:28:05 +01:00
|
|
|
if (type == TypePermissions)
|
|
|
|
|
return BehaviorSilent;
|
|
|
|
|
if (type == TypeContents) {
|
|
|
|
|
if (state == TriggerInternal && !isModified())
|
|
|
|
|
return BehaviorSilent;
|
|
|
|
|
return BehaviorAsk;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
2010-03-19 10:28:05 +01:00
|
|
|
return BehaviorAsk;
|
|
|
|
|
}
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2010-03-19 10:28:05 +01:00
|
|
|
void BaseTextDocument::reload(ReloadFlag flag, ChangeType type)
|
|
|
|
|
{
|
|
|
|
|
if (flag == FlagIgnore)
|
|
|
|
|
return;
|
|
|
|
|
if (type == TypePermissions) {
|
2010-04-08 16:07:23 +02:00
|
|
|
checkPermissions();
|
2010-03-19 10:28:05 +01:00
|
|
|
} else {
|
2008-12-02 12:01:29 +01:00
|
|
|
reload();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-09 14:47:18 +02:00
|
|
|
void BaseTextDocument::setSyntaxHighlighter(SyntaxHighlighter *highlighter)
|
2008-12-02 12:01:29 +01:00
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
if (d->m_highlighter)
|
|
|
|
|
delete d->m_highlighter;
|
|
|
|
|
d->m_highlighter = highlighter;
|
|
|
|
|
d->m_highlighter->setParent(this);
|
|
|
|
|
d->m_highlighter->setDocument(d->m_document);
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
2008-12-09 17:43:31 +01:00
|
|
|
|
|
|
|
|
|
2009-09-09 16:37:09 +02:00
|
|
|
void BaseTextDocument::cleanWhitespace(const QTextCursor &cursor)
|
2008-12-09 17:43:31 +01:00
|
|
|
{
|
2009-09-09 16:37:09 +02:00
|
|
|
bool hasSelection = cursor.hasSelection();
|
|
|
|
|
QTextCursor copyCursor = cursor;
|
2010-09-07 15:55:06 +02:00
|
|
|
copyCursor.setVisualNavigation(false);
|
2009-09-09 16:37:09 +02:00
|
|
|
copyCursor.beginEditBlock();
|
|
|
|
|
cleanWhitespace(copyCursor, true, true);
|
|
|
|
|
if (!hasSelection)
|
|
|
|
|
ensureFinalNewLine(copyCursor);
|
|
|
|
|
copyCursor.endEditBlock();
|
2008-12-09 17:43:31 +01:00
|
|
|
}
|
|
|
|
|
|
2010-05-28 14:06:57 +02:00
|
|
|
void BaseTextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, bool inEntireDocument)
|
2008-12-02 12:01:29 +01:00
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(d->m_document->documentLayout());
|
2010-09-07 15:55:06 +02:00
|
|
|
Q_ASSERT(cursor.visualNavigation() == false);
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2010-10-29 15:20:10 +02:00
|
|
|
QTextBlock block = d->m_document->findBlock(cursor.selectionStart());
|
2009-09-09 16:37:09 +02:00
|
|
|
QTextBlock end;
|
|
|
|
|
if (cursor.hasSelection())
|
2010-10-29 15:20:10 +02:00
|
|
|
end = d->m_document->findBlock(cursor.selectionEnd()-1).next();
|
2009-09-09 16:37:09 +02:00
|
|
|
|
|
|
|
|
while (block.isValid() && block != end) {
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2009-12-11 19:49:17 +01:00
|
|
|
if (inEntireDocument || block.revision() != documentLayout->lastSaveRevision) {
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
QString blockText = block.text();
|
2010-10-29 15:20:10 +02:00
|
|
|
if (int trailing = d->m_tabSettings.trailingWhitespaces(blockText)) {
|
2008-12-02 12:01:29 +01:00
|
|
|
cursor.setPosition(block.position() + block.length() - 1);
|
|
|
|
|
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, trailing);
|
|
|
|
|
cursor.removeSelectedText();
|
|
|
|
|
}
|
2010-10-29 15:20:10 +02:00
|
|
|
if (cleanIndentation && !d->m_tabSettings.isIndentationClean(block)) {
|
2008-12-02 12:01:29 +01:00
|
|
|
cursor.setPosition(block.position());
|
2010-10-29 15:20:10 +02:00
|
|
|
int firstNonSpace = d->m_tabSettings.firstNonSpace(blockText);
|
2008-12-02 12:01:29 +01:00
|
|
|
if (firstNonSpace == blockText.length()) {
|
|
|
|
|
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
|
|
|
|
cursor.removeSelectedText();
|
|
|
|
|
} else {
|
2010-10-29 15:20:10 +02:00
|
|
|
int column = d->m_tabSettings.columnAt(blockText, firstNonSpace);
|
2008-12-02 12:01:29 +01:00
|
|
|
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace);
|
2010-10-29 15:20:10 +02:00
|
|
|
QString indentationString = d->m_tabSettings.indentationString(0, column, block);
|
2008-12-02 12:01:29 +01:00
|
|
|
cursor.insertText(indentationString);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
block = block.next();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseTextDocument::ensureFinalNewLine(QTextCursor& cursor)
|
|
|
|
|
{
|
|
|
|
|
cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
|
|
|
|
|
bool emptyFile = !cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
|
|
|
|
|
|
|
|
|
|
if (!emptyFile && cursor.selectedText().at(0) != QChar::ParagraphSeparator)
|
|
|
|
|
{
|
|
|
|
|
cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
|
|
|
|
|
cursor.insertText(QLatin1String("\n"));
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-05-28 14:06:57 +02:00
|
|
|
|
|
|
|
|
void BaseTextDocument::documentClosing()
|
|
|
|
|
{
|
2010-10-29 15:20:10 +02:00
|
|
|
QTextBlock block = d->m_document->begin();
|
2010-05-28 14:06:57 +02:00
|
|
|
while (block.isValid()) {
|
|
|
|
|
if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData()))
|
|
|
|
|
data->documentClosing();
|
|
|
|
|
block = block.next();
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-10-29 15:20:10 +02:00
|
|
|
|
|
|
|
|
} // namespace TextEditor
|
|
|
|
|
|
|
|
|
|
#include "basetextdocument.moc"
|