1 Star 0 Fork 0

旷野芦苇殇/texteditor

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
textdocument.cpp 36.28 KB
一键复制 编辑 原始数据 按行查看 历史
旷野芦苇殇 提交于 2025-03-05 21:22 +08:00 . 同步15.01的AI 部分代码
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "textdocument.h"
#include "extraencodingsettings.h"
#include "fontsettings.h"
#include "storagesettings.h"
#include "syntaxhighlighter.h"
#include "tabsettings.h"
#include "textdocumentlayout.h"
#include "texteditor.h"
#include "texteditorconstants.h"
#include "textindenter.h"
#include "typingsettings.h"
#include "utils/mimeutils.h"
#include "utils/textutils.h"
#include "codeformatter.h"
#include <QAction>
#include <QApplication>
#include <QDir>
#include <QFileInfo>
#include <QFutureInterface>
#include <QScrollBar>
#include <QStringList>
#include <QTextCodec>
#include "core/coreconstants.h"
#include "core/icore.h"
#include "utils/qtcassert.h"
using namespace Core;
using namespace Utils;
/*!
\class TextEditor::BaseTextDocument
\brief The BaseTextDocument class is the base class for QTextDocument based documents.
It is the base class for documents used by implementations of the BaseTextEditor class,
and contains basic functions for retrieving text content and markers (like bookmarks).
Subclasses of BaseTextEditor can either use BaseTextDocument as is (and this is the default),
or created subclasses of BaseTextDocument if they have special requirements.
*/
namespace TextEditor {
class TextDocumentPrivate
{
public:
TextDocumentPrivate()
: m_indenter(new TextIndenter(&m_document))
{
}
MultiTextCursor indentOrUnindent(const MultiTextCursor &cursor, bool doIndent, const TabSettings &tabSettings);
void resetRevisions();
void updateRevisions();
public:
FilePath m_defaultPath;
QString m_suggestedFileName;
TypingSettings m_typingSettings;
StorageSettings m_storageSettings;
TabSettings m_tabSettings;
ExtraEncodingSettings m_extraEncodingSettings;
FontSettings m_fontSettings;
bool m_fontSettingsNeedsApply = false; // for applying font settings delayed till an editor becomes visible
QTextDocument m_document;
SyntaxHighlighter *m_highlighter = nullptr;
CompletionAssistProvider *m_completionAssistProvider = nullptr;
CompletionAssistProvider *m_functionHintAssistProvider = nullptr;
IAssistProvider *m_quickFixProvider = nullptr;
QScopedPointer<Indenter> m_indenter;
//QScopedPointer<Formatter> m_formatter;
QScopedPointer<CodeFormatter> m_codeFormatter;
int m_autoSaveRevision = -1;
bool m_silentReload = false;
TextMarks m_marksCache; // Marks not owned
};
MultiTextCursor TextDocumentPrivate::indentOrUnindent(const MultiTextCursor &cursors,
bool doIndent,
const TabSettings &tabSettings)
{
MultiTextCursor result;
bool first = true;
for (const QTextCursor &textCursor : cursors) {
QTextCursor cursor = textCursor;
if (first) {
cursor.beginEditBlock();
first = false;
} else {
cursor.joinPreviousEditBlock();
}
// Indent or unindent the selected lines
int pos = cursor.position();
int column = tabSettings.columnAt(cursor.block().text(), cursor.positionInBlock());
int anchor = cursor.anchor();
int start = qMin(anchor, pos);
int end = qMax(anchor, pos);
QTextBlock startBlock = m_document.findBlock(start);
QTextBlock endBlock = m_document.findBlock(qMax(end - 1, 0)).next();
const bool cursorAtBlockStart = (cursor.position() == startBlock.position());
const bool anchorAtBlockStart = (cursor.anchor() == startBlock.position());
const bool oneLinePartial = (startBlock.next() == endBlock)
&& (start > startBlock.position()
|| end < endBlock.position() - 1)
&& !cursors.hasMultipleCursors();
// Make sure one line selection will get processed in "for" loop
if (startBlock == endBlock)
endBlock = endBlock.next();
if (cursor.hasSelection()) {
if (oneLinePartial) {
cursor.removeSelectedText();
} else {
for (QTextBlock block = startBlock; block != endBlock; block = block.next()) {
const QString text = block.text();
int indentPosition = tabSettings.lineIndentPosition(text);
if (!doIndent && !indentPosition)
indentPosition = TabSettings::firstNonSpace(text);
int targetColumn
= tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition),
doIndent);
cursor.setPosition(block.position() + indentPosition);
cursor.insertText(tabSettings.indentationString(0, targetColumn, 0, block));
cursor.setPosition(block.position());
cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
// make sure that selection that begins in first column stays at first column
// even if we insert text at first column
cursor = textCursor;
if (cursorAtBlockStart) {
cursor.setPosition(startBlock.position(), QTextCursor::KeepAnchor);
} else if (anchorAtBlockStart) {
cursor.setPosition(startBlock.position(), QTextCursor::MoveAnchor);
cursor.setPosition(textCursor.position(), QTextCursor::KeepAnchor);
}
}
} else {
QString text = startBlock.text();
int indentPosition = tabSettings.positionAtColumn(text, column, nullptr, true);
int spaces = tabSettings.spacesLeftFromPosition(text, indentPosition);
int startColumn = tabSettings.columnAt(text, indentPosition - spaces);
int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition),
doIndent);
cursor.setPosition(startBlock.position() + indentPosition);
cursor.setPosition(startBlock.position() + indentPosition - spaces,
QTextCursor::KeepAnchor);
cursor.removeSelectedText();
cursor.insertText(
tabSettings.indentationString(startColumn, targetColumn, 0, startBlock));
}
cursor.endEditBlock();
result.addCursor(cursor);
}
return result;
}
void TextDocumentPrivate::resetRevisions()
{
auto documentLayout = qobject_cast<TextDocumentLayout*>(m_document.documentLayout());
QTC_ASSERT(documentLayout, return);
documentLayout->lastSaveRevision = m_document.revision();
for (QTextBlock block = m_document.begin(); block.isValid(); block = block.next())
block.setRevision(documentLayout->lastSaveRevision);
}
void TextDocumentPrivate::updateRevisions()
{
auto documentLayout = qobject_cast<TextDocumentLayout*>(m_document.documentLayout());
QTC_ASSERT(documentLayout, return);
int oldLastSaveRevision = documentLayout->lastSaveRevision;
documentLayout->lastSaveRevision = m_document.revision();
if (oldLastSaveRevision != documentLayout->lastSaveRevision) {
for (QTextBlock block = m_document.begin(); block.isValid(); block = block.next()) {
if (block.revision() < 0 || block.revision() != oldLastSaveRevision)
block.setRevision(-documentLayout->lastSaveRevision - 1);
else
block.setRevision(documentLayout->lastSaveRevision);
}
}
}
///////////////////////////////////////////////////////////////////////
//
// BaseTextDocument
//
///////////////////////////////////////////////////////////////////////
TextDocument::TextDocument(Id id)
: d(new TextDocumentPrivate)
{
d->m_document.setParent(this);
connect(&d->m_document, &QTextDocument::modificationChanged,
this, &TextDocument::modificationChanged);
connect(&d->m_document, &QTextDocument::contentsChanged,
this, &Core::IDocument::contentsChanged);
connect(&d->m_document, &QTextDocument::contentsChange,
this, &TextDocument::contentsChangedWithPosition);
// set new document layout
QTextOption opt = d->m_document.defaultTextOption();
opt.setTextDirection(Qt::LeftToRight);
opt.setFlags(opt.flags() | QTextOption::IncludeTrailingSpaces
| QTextOption::AddSpaceForLineAndParagraphSeparators
);
d->m_document.setDefaultTextOption(opt);
d->m_document.setDocumentLayout(new TextDocumentLayout(&d->m_document));
if (id.isValid())
setId(id);
setSuspendAllowed(true);
}
TextDocument::~TextDocument()
{
delete d;
}
QMap<FilePath, QString> TextDocument::openedTextDocumentContents()
{
QMap<FilePath, QString> workingCopy;
return workingCopy;
}
QMap<FilePath, QTextCodec *> TextDocument::openedTextDocumentEncodings()
{
QMap<FilePath, QTextCodec *> workingCopy;
return workingCopy;
}
TextDocument *TextDocument::currentTextDocument()
{
//return qobject_cast<TextDocument *>(EditorManager::currentDocument());
return nullptr;
}
TextDocument *TextDocument::textDocumentForFilePath(const Utils::FilePath &filePath)
{
return nullptr;
}
QString TextDocument::convertToPlainText(const QString &rawText)
{
// This is basically a copy of QTextDocument::toPlainText but since toRawText returns a
// text containing formating characters and toPlainText replaces non breaking spaces, we
// provide our own plain text conversion to be able to save and copy document content
// containing non breaking spaces.
QString txt = rawText;
QChar *uc = txt.data();
QChar *e = uc + txt.size();
for (; uc != e; ++uc) {
switch (uc->unicode()) {
case 0xfdd0: // QTextBeginningOfFrame
case 0xfdd1: // QTextEndOfFrame
case QChar::ParagraphSeparator:
case QChar::LineSeparator:
*uc = QLatin1Char('\n');
break;
default:;
}
}
return txt;
}
QString TextDocument::plainText() const
{
return convertToPlainText(d->m_document.toRawText());
}
QString TextDocument::textAt(int pos, int length) const
{
return Utils::Text::textAt(QTextCursor(document()), pos, length);
}
QChar TextDocument::characterAt(int pos) const
{
return document()->characterAt(pos);
}
void TextDocument::setTypingSettings(const TypingSettings &typingSettings)
{
d->m_typingSettings = typingSettings;
}
void TextDocument::setStorageSettings(const StorageSettings &storageSettings)
{
d->m_storageSettings = storageSettings;
}
const TypingSettings &TextDocument::typingSettings() const
{
return d->m_typingSettings;
}
const StorageSettings &TextDocument::storageSettings() const
{
return d->m_storageSettings;
}
void TextDocument::setTabSettings(const TabSettings &newTabSettings)
{
if (newTabSettings == d->m_tabSettings)
return;
d->m_tabSettings = newTabSettings;
emit tabSettingsChanged();
}
TabSettings TextDocument::tabSettings() const
{
return d->m_tabSettings;
}
void TextDocument::setFontSettings(const FontSettings &fontSettings)
{
if (fontSettings == d->m_fontSettings)
return;
d->m_fontSettings = fontSettings;
d->m_fontSettingsNeedsApply = true;
emit fontSettingsChanged();
}
QAction *TextDocument::createDiffAgainstCurrentFileAction(
QObject *parent, const std::function<Utils::FilePath()> &filePath)
{
const auto diffAgainstCurrentFile = [filePath]() {
//auto diffService = DiffService::instance();
auto textDocument = TextEditor::TextDocument::currentTextDocument();
const QString leftFilePath = textDocument ? textDocument->filePath().toString() : QString();
const QString rightFilePath = filePath().toString();
};
auto diffAction = new QAction(tr("Diff Against Current File"), parent);
QObject::connect(diffAction, &QAction::triggered, parent, diffAgainstCurrentFile);
return diffAction;
}
void TextDocument::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
{
QTextCursor cursor(&d->m_document);
cursor.setPosition(suggestion->currentPosition());
const QTextBlock block = cursor.block();
TextDocumentLayout::userData(block)->insertSuggestion(std::move(suggestion));
TextDocumentLayout::updateSuggestionFormats(block, fontSettings());
updateLayout();
}
#ifdef WITH_TESTS
void TextDocument::setSilentReload()
{
d->m_silentReload = true;
}
#endif
void TextDocument::triggerPendingUpdates()
{
if (d->m_fontSettingsNeedsApply)
applyFontSettings();
}
void TextDocument::setCompletionAssistProvider(CompletionAssistProvider *provider)
{
d->m_completionAssistProvider = provider;
}
CompletionAssistProvider *TextDocument::completionAssistProvider() const
{
return d->m_completionAssistProvider;
}
void TextDocument::setFunctionHintAssistProvider(CompletionAssistProvider *provider)
{
d->m_functionHintAssistProvider = provider;
}
CompletionAssistProvider *TextDocument::functionHintAssistProvider() const
{
return d->m_functionHintAssistProvider;
}
void TextDocument::setQuickFixAssistProvider(IAssistProvider *provider) const
{
d->m_quickFixProvider = provider;
}
IAssistProvider *TextDocument::quickFixAssistProvider() const
{
return d->m_quickFixProvider;
}
void TextDocument::applyFontSettings()
{
d->m_fontSettingsNeedsApply = false;
if (d->m_highlighter) {
d->m_highlighter->setFontSettings(d->m_fontSettings);
d->m_highlighter->rehighlight();
}
}
const FontSettings &TextDocument::fontSettings() const
{
return d->m_fontSettings;
}
void TextDocument::setExtraEncodingSettings(const ExtraEncodingSettings &extraEncodingSettings)
{
d->m_extraEncodingSettings = extraEncodingSettings;
}
void TextDocument::autoIndent(const QTextCursor &cursor, QChar typedChar, int currentCursorPosition)
{
d->m_indenter->indent(cursor, typedChar, tabSettings(), currentCursorPosition);
}
void TextDocument::autoReindent(const QTextCursor &cursor, int currentCursorPosition)
{
d->m_indenter->reindent(cursor, tabSettings(), currentCursorPosition);
}
void TextDocument::autoFormatOrIndent(const QTextCursor &cursor)
{
d->m_indenter->autoIndent(cursor, tabSettings());
}
Utils::MultiTextCursor TextDocument::indent(const Utils::MultiTextCursor &cursor)
{
return d->indentOrUnindent(cursor, true, tabSettings());
}
Utils::MultiTextCursor TextDocument::unindent(const Utils::MultiTextCursor &cursor)
{
return d->indentOrUnindent(cursor, false, tabSettings());
}
void TextDocument::setFormatter(CodeFormatter *formatter)
{
d->m_codeFormatter.reset(formatter);
}
void TextDocument::autoFormat(QTextCursor &cursor)
{
using namespace Utils::Text;
if (!d->m_codeFormatter)
return;
QTextDocument *doc = cursor.document();
bool hasSelection = cursor.hasSelection();
QTextBlock block = hasSelection?doc->findBlock(cursor.selectionStart()):doc->begin();
const QTextBlock end = hasSelection?doc->findBlock(cursor.selectionEnd()).next():doc->end();
cursor.beginEditBlock();
const TextEditor::TabSettings &tabSettings = this->tabSettings();
//QmlJSTools::CreatorCodeFormatter codeFormatter(tabSettings);
d->m_codeFormatter->updateStateUntil(block);
do {
int depth = d->m_codeFormatter->indentFor(block);
if (depth != -1) {
if (QStringView(block.text()).trimmed().isEmpty()) {
// we do not want to indent empty lines (as one is indentent when pressing tab
// assuming that the user will start writing something), and get rid of that
// space if one had pressed tab in an empty line just before refactoring.
// If depth == -1 (inside a multiline string for example) leave the spaces.
depth = 0;
}
tabSettings.indentLine(block, depth);
}
d->m_codeFormatter->updateLineStateChange(block);
block = block.next();
} while (block.isValid() && block != end);
cursor.endEditBlock();
/*if (QFutureWatcher<ChangeSet> *watcher = d->m_formatter->format(cursor, tabSettings())) {
connect(watcher, &QFutureWatcher<ChangeSet>::finished, this, [this, watcher]() {
if (!watcher->isCanceled())
applyChangeSet(watcher->result());
delete watcher;
});
}*/
}
/*bool TextDocument::applyChangeSet(const ChangeSet &changeSet)
{
if (changeSet.isEmpty())
return true;
RefactoringChanges changes;
const RefactoringFilePtr file = changes.file(filePath());
file->setChangeSet(changeSet);
return file->apply();
}*/
// the blocks list must be sorted
void TextDocument::setIfdefedOutBlocks(const QList<BlockRange> &blocks)
{
QTextDocument *doc = document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return);
bool needUpdate = false;
QTextBlock block = doc->firstBlock();
int rangeNumber = 0;
int braceDepthDelta = 0;
while (block.isValid()) {
bool cleared = false;
bool set = false;
if (rangeNumber < blocks.size()) {
const BlockRange &range = blocks.at(rangeNumber);
if (block.position() >= range.first()
&& ((block.position() + block.length() - 1) <= range.last() || !range.last()))
set = TextDocumentLayout::setIfdefedOut(block);
else
cleared = TextDocumentLayout::clearIfdefedOut(block);
if (block.contains(range.last()))
++rangeNumber;
} else {
cleared = TextDocumentLayout::clearIfdefedOut(block);
}
if (cleared || set) {
needUpdate = true;
int delta = TextDocumentLayout::braceDepthDelta(block);
if (cleared)
braceDepthDelta += delta;
else if (set)
braceDepthDelta -= delta;
}
if (braceDepthDelta) {
TextDocumentLayout::changeBraceDepth(block,braceDepthDelta);
TextDocumentLayout::changeFoldingIndent(block, braceDepthDelta); // ### C++ only, refactor!
}
block = block.next();
}
if (needUpdate)
documentLayout->requestUpdate();
#ifdef WITH_TESTS
emit ifdefedOutBlocksChanged(blocks);
#endif
}
const ExtraEncodingSettings &TextDocument::extraEncodingSettings() const
{
return d->m_extraEncodingSettings;
}
void TextDocument::setIndenter(Indenter *indenter)
{
// clear out existing code formatter data
for (QTextBlock it = document()->begin(); it.isValid(); it = it.next()) {
TextBlockUserData *userData = TextDocumentLayout::textUserData(it);
if (userData)
userData->setCodeFormatterData(nullptr);
}
d->m_indenter.reset(indenter);
}
Indenter *TextDocument::indenter() const
{
return d->m_indenter.data();
}
bool TextDocument::isSaveAsAllowed() const
{
return true;
}
FilePath TextDocument::fallbackSaveAsPath() const
{
return d->m_defaultPath;
}
QString TextDocument::fallbackSaveAsFileName() const
{
return d->m_suggestedFileName;
}
void TextDocument::setFallbackSaveAsPath(const FilePath &defaultPath)
{
d->m_defaultPath = defaultPath;
}
void TextDocument::setFallbackSaveAsFileName(const QString &suggestedFileName)
{
d->m_suggestedFileName = suggestedFileName;
}
QTextDocument *TextDocument::document() const
{
return &d->m_document;
}
SyntaxHighlighter *TextDocument::syntaxHighlighter() const
{
return d->m_highlighter;
}
/*!
* Saves the document to the file specified by \a fileName. If errors occur,
* \a errorString contains their cause.
* \a autoSave returns whether this function was called by the automatic save routine.
* If \a autoSave is true, the cursor will be restored and some signals suppressed
* and we do not clean up the text file (cleanWhitespace(), ensureFinalNewLine()).
*/
bool TextDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
{
QTextCursor cursor(&d->m_document);
// When autosaving, we don't want to modify the document/location under the user's fingers.
TextEditorWidget *editorWidget = nullptr;
int savedPosition = 0;
int savedAnchor = 0;
int savedVScrollBarValue = 0;
int savedHScrollBarValue = 0;
int undos = d->m_document.availableUndoSteps();
// When saving the current editor, make sure to maintain the cursor and scroll bar
// positions for undo
/*if (BaseTextEditor *editor = BaseTextEditor::currentTextEditor()) {
if (editor->document() == this) {
editorWidget = editor->editorWidget();
QTextCursor cur = editor->textCursor();
savedPosition = cur.position();
savedAnchor = cur.anchor();
savedVScrollBarValue = editorWidget->verticalScrollBar()->value();
savedHScrollBarValue = editorWidget->horizontalScrollBar()->value();
cursor.setPosition(cur.position());
}
}*/
if (!autoSave) {
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::Start);
if (d->m_storageSettings.m_cleanWhitespace) {
cleanWhitespace(cursor,
d->m_storageSettings.m_inEntireDocument,
d->m_storageSettings.m_cleanIndentation);
}
if (d->m_storageSettings.m_addFinalNewLine)
ensureFinalNewLine(cursor);
cursor.endEditBlock();
}
const Utils::FilePath &savePath = filePath.isEmpty() ? this->filePath() : filePath;
// check if UTF8-BOM has to be added or removed
Utils::TextFileFormat saveFormat = format();
if (saveFormat.codec->name() == "UTF-8" && supportsUtf8Bom()) {
switch (d->m_extraEncodingSettings.m_utf8BomSetting) {
case ExtraEncodingSettings::AlwaysAdd:
saveFormat.hasUtf8Bom = true;
break;
case ExtraEncodingSettings::OnlyKeep:
break;
case ExtraEncodingSettings::AlwaysDelete:
saveFormat.hasUtf8Bom = false;
break;
}
}
const bool ok = write(savePath, saveFormat, plainText(), errorString);
// restore text cursor and scroll bar positions
if (autoSave && undos < d->m_document.availableUndoSteps()) {
d->m_document.undo();
if (editorWidget) {
QTextCursor cur = editorWidget->textCursor();
cur.setPosition(savedAnchor);
cur.setPosition(savedPosition, QTextCursor::KeepAnchor);
editorWidget->verticalScrollBar()->setValue(savedVScrollBarValue);
editorWidget->horizontalScrollBar()->setValue(savedHScrollBarValue);
editorWidget->setTextCursor(cur);
}
}
if (!ok)
return false;
d->m_autoSaveRevision = d->m_document.revision();
if (autoSave)
return true;
// inform about the new filename
d->m_document.setModified(false); // also triggers update of the block revisions
setFilePath(savePath.absoluteFilePath());
emit changed();
return true;
}
QByteArray TextDocument::contents() const
{
return plainText().toUtf8();
}
bool TextDocument::setContents(const QByteArray &contents)
{
return setPlainText(QString::fromUtf8(contents));
}
bool TextDocument::shouldAutoSave() const
{
return d->m_autoSaveRevision != d->m_document.revision();
}
void TextDocument::setFilePath(const Utils::FilePath &newName)
{
if (newName == filePath())
return;
IDocument::setFilePath(newName.absoluteFilePath().cleanPath());
}
IDocument::ReloadBehavior TextDocument::reloadBehavior(ChangeTrigger state, ChangeType type) const
{
if (d->m_silentReload)
return IDocument::BehaviorSilent;
return BaseTextDocument::reloadBehavior(state, type);
}
bool TextDocument::isModified() const
{
return d->m_document.isModified();
}
Core::IDocument::OpenResult TextDocument::open(QString *errorString,
const Utils::FilePath &filePath,
const Utils::FilePath &realFilePath)
{
emit aboutToOpen(filePath, realFilePath);
OpenResult success = openImpl(errorString, filePath, realFilePath, /*reload =*/ false);
if (success == OpenResult::Success) {
setMimeType(Utils::mimeTypeForFile(filePath, MimeMatchMode::MatchDefaultAndRemote).name());
emit openFinishedSuccessfully();
}
return success;
}
Core::IDocument::OpenResult TextDocument::openImpl(QString *errorString,
const Utils::FilePath &filePath,
const Utils::FilePath &realFilePath,
bool reload)
{
QStringList content;
ReadResult readResult = Utils::TextFileFormat::ReadIOError;
if (!filePath.isEmpty()) {
readResult = read(realFilePath, &content, errorString);
const int chunks = content.size();
// Don't call setUndoRedoEnabled(true) when reload is true and filenames are different,
// since it will reset the undo's clear index
if (!reload || filePath == realFilePath)
d->m_document.setUndoRedoEnabled(reload);
QTextCursor c(&d->m_document);
c.beginEditBlock();
if (reload) {
c.select(QTextCursor::Document);
c.removeSelectedText();
} else {
d->m_document.clear();
}
if (chunks == 1) {
c.insertText(content.at(0));
} else if (chunks > 1) {
QFutureInterface<void> interface;
interface.setProgressRange(0, chunks);
interface.reportStarted();
for (int i = 0; i < chunks; ++i) {
c.insertText(content.at(i));
interface.setProgressValue(i + 1);
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
interface.reportFinished();
}
c.endEditBlock();
// Don't call setUndoRedoEnabled(true) when reload is true and filenames are different,
// since it will reset the undo's clear index
if (!reload || filePath == realFilePath)
d->m_document.setUndoRedoEnabled(true);
auto documentLayout =
qobject_cast<TextDocumentLayout*>(d->m_document.documentLayout());
QTC_ASSERT(documentLayout, return OpenResult::CannotHandle);
documentLayout->lastSaveRevision = d->m_autoSaveRevision = d->m_document.revision();
d->updateRevisions();
d->m_document.setModified(filePath != realFilePath);
setFilePath(filePath);
}
if (readResult == Utils::TextFileFormat::ReadIOError)
return OpenResult::ReadError;
return OpenResult::Success;
}
bool TextDocument::reload(QString *errorString, QTextCodec *codec)
{
QTC_ASSERT(codec, return false);
setCodec(codec);
return reload(errorString);
}
bool TextDocument::reload(QString *errorString)
{
return reload(errorString, filePath());
}
bool TextDocument::reload(QString *errorString, const FilePath &realFilePath)
{
emit aboutToReload();
auto documentLayout =
qobject_cast<TextDocumentLayout*>(d->m_document.documentLayout());
TextMarks marks;
if (documentLayout)
marks = documentLayout->documentClosing(); // removes text marks non-permanently
bool success = openImpl(errorString, filePath(), realFilePath, /*reload =*/true)
== OpenResult::Success;
if (documentLayout)
documentLayout->documentReloaded(marks, this); // re-adds text marks
emit reloadFinished(success);
return success;
}
bool TextDocument::setPlainText(const QString &text)
{
if (text.size() > 10*1024*1024) {
document()->setPlainText(TextEditorWidget::msgTextTooLarge(text.size()));
d->resetRevisions();
document()->setModified(false);
return false;
}
document()->setPlainText(text);
d->resetRevisions();
document()->setModified(false);
return true;
}
bool TextDocument::reload(QString *errorString, ReloadFlag flag, ChangeType type)
{
if (flag == FlagIgnore) {
if (type != TypeContents)
return true;
const bool wasModified = document()->isModified();
{
// hack to ensure we clean the clear state in QTextDocument
document()->setModified(false);
document()->setModified(true);
}
if (!wasModified)
modificationChanged(true);
return true;
}
return reload(errorString);
}
void TextDocument::setSyntaxHighlighter(SyntaxHighlighter *highlighter)
{
if (d->m_highlighter)
delete d->m_highlighter;
d->m_highlighter = highlighter;
d->m_highlighter->setParent(this);
d->m_highlighter->setDocument(&d->m_document);
}
void TextDocument::cleanWhitespace(const QTextCursor &cursor)
{
bool hasSelection = cursor.hasSelection();
QTextCursor copyCursor = cursor;
copyCursor.setVisualNavigation(false);
copyCursor.beginEditBlock();
cleanWhitespace(copyCursor, true, true);
if (!hasSelection)
ensureFinalNewLine(copyCursor);
copyCursor.endEditBlock();
}
void TextDocument::cleanWhitespace(QTextCursor &cursor, bool inEntireDocument,
bool cleanIndentation)
{
const bool removeTrailingWhitespace = d->m_storageSettings.removeTrailingWhitespace(filePath().fileName());
auto documentLayout = qobject_cast<TextDocumentLayout*>(d->m_document.documentLayout());
Q_ASSERT(cursor.visualNavigation() == false);
QTextBlock block = d->m_document.findBlock(cursor.selectionStart());
QTextBlock end;
if (cursor.hasSelection())
end = d->m_document.findBlock(cursor.selectionEnd()-1).next();
QVector<QTextBlock> blocks;
while (block.isValid() && block != end) {
if (inEntireDocument || block.revision() != documentLayout->lastSaveRevision) {
blocks.append(block);
}
block = block.next();
}
if (blocks.isEmpty())
return;
const TabSettings currentTabSettings = tabSettings();
const IndentationForBlock &indentations
= d->m_indenter->indentationForBlocks(blocks, currentTabSettings);
for (QTextBlock block : std::as_const(blocks)) {
QString blockText = block.text();
if (removeTrailingWhitespace)
TabSettings::removeTrailingWhitespace(cursor, block);
const int indent = indentations[block.blockNumber()];
if (cleanIndentation && !currentTabSettings.isIndentationClean(block, indent)) {
cursor.setPosition(block.position());
const int firstNonSpace = TabSettings::firstNonSpace(blockText);
if (firstNonSpace == blockText.length()) {
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
} else {
int column = currentTabSettings.columnAt(blockText, firstNonSpace);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace);
QString indentationString = currentTabSettings.indentationString(0, column, column - indent, block);
cursor.insertText(indentationString);
}
}
}
}
void TextDocument::ensureFinalNewLine(QTextCursor& cursor)
{
if (!d->m_storageSettings.m_addFinalNewLine)
return;
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"));
}
}
void TextDocument::modificationChanged(bool modified)
{
// we only want to update the block revisions when going back to the saved version,
// e.g. with undo
if (!modified)
d->updateRevisions();
emit changed();
}
void TextDocument::updateLayout() const
{
auto documentLayout = qobject_cast<TextDocumentLayout*>(d->m_document.documentLayout());
QTC_ASSERT(documentLayout, return);
documentLayout->requestUpdate();
}
void TextDocument::scheduleUpdateLayout() const
{
auto documentLayout = qobject_cast<TextDocumentLayout*>(d->m_document.documentLayout());
QTC_ASSERT(documentLayout, return);
documentLayout->scheduleUpdate();
}
TextMarks TextDocument::marks() const
{
return d->m_marksCache;
}
bool TextDocument::addMark(TextMark *mark)
{
if (mark->baseTextDocument())
return false;
QTC_ASSERT(mark->lineNumber() >= 1, return false);
int blockNumber = mark->lineNumber() - 1;
auto documentLayout = qobject_cast<TextDocumentLayout*>(d->m_document.documentLayout());
QTC_ASSERT(documentLayout, return false);
QTextBlock block = d->m_document.findBlockByNumber(blockNumber);
if (block.isValid()) {
TextBlockUserData *userData = TextDocumentLayout::userData(block);
userData->addMark(mark);
d->m_marksCache.append(mark);
mark->updateLineNumber(blockNumber + 1);
QTC_CHECK(mark->lineNumber() == blockNumber + 1); // Checks that the base class is called
mark->updateBlock(block);
mark->setBaseTextDocument(this);
if (!mark->isVisible())
return true;
// Update document layout
bool fullUpdate = !documentLayout->hasMarks;
documentLayout->hasMarks = true;
if (!documentLayout->hasLocationMarker && mark->isLocationMarker()) {
documentLayout->hasLocationMarker = true;
fullUpdate = true;
}
if (fullUpdate)
documentLayout->scheduleUpdate();
else
documentLayout->requestExtraAreaUpdate();
return true;
}
return false;
}
TextMarks TextDocument::marksAt(int line) const
{
QTC_ASSERT(line >= 1, return TextMarks());
int blockNumber = line - 1;
QTextBlock block = d->m_document.findBlockByNumber(blockNumber);
if (block.isValid()) {
if (TextBlockUserData *userData = TextDocumentLayout::textUserData(block))
return userData->marks();
}
return TextMarks();
}
void TextDocument::removeMarkFromMarksCache(TextMark *mark)
{
auto documentLayout = qobject_cast<TextDocumentLayout*>(d->m_document.documentLayout());
QTC_ASSERT(documentLayout, return);
d->m_marksCache.removeAll(mark);
auto scheduleLayoutUpdate = [documentLayout](){
// make sure all destructors that may directly or indirectly call this function are
// completed before updating.
QMetaObject::invokeMethod(documentLayout, &QPlainTextDocumentLayout::requestUpdate,
Qt::QueuedConnection);
};
if (mark->isLocationMarker()) {
documentLayout->hasLocationMarker = false;
scheduleLayoutUpdate();
}
if (d->m_marksCache.isEmpty()) {
documentLayout->hasMarks = false;
scheduleLayoutUpdate();
return;
}
if (!mark->isVisible())
return;
documentLayout->requestExtraAreaUpdate();
}
void TextDocument::removeMark(TextMark *mark)
{
QTextBlock block = d->m_document.findBlockByNumber(mark->lineNumber() - 1);
if (auto data = static_cast<TextBlockUserData *>(block.userData())) {
if (!data->removeMark(mark))
qDebug() << "Could not find mark" << mark << "on line" << mark->lineNumber();
}
removeMarkFromMarksCache(mark);
emit markRemoved(mark);
mark->setBaseTextDocument(nullptr);
scheduleUpdateLayout();
}
void TextDocument::updateMark(TextMark *mark)
{
QTextBlock block = d->m_document.findBlockByNumber(mark->lineNumber() - 1);
if (block.isValid()) {
TextBlockUserData *userData = TextDocumentLayout::userData(block);
// re-evaluate priority
userData->removeMark(mark);
userData->addMark(mark);
}
scheduleUpdateLayout();
}
void TextDocument::moveMark(TextMark *mark, int previousLine)
{
QTextBlock block = d->m_document.findBlockByNumber(previousLine - 1);
if (TextBlockUserData *data = TextDocumentLayout::textUserData(block)) {
if (!data->removeMark(mark))
qDebug() << "Could not find mark" << mark << "on line" << previousLine;
}
removeMarkFromMarksCache(mark);
mark->setBaseTextDocument(nullptr);
addMark(mark);
}
} // namespace TextEditor
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/fengzhu2007/texteditor.git
git@gitee.com:fengzhu2007/texteditor.git
fengzhu2007
texteditor
texteditor
main

搜索帮助