forked from qt-creator/qt-creator
Beautifier: Move formatting tools to TextEditor
Formatting is moved from Beautifier plugin to formattexteditor.h/.cpp. Diff and Differ classes are extracted from DiffEditor to Utils to prevent extra TextEditor dependencies. This change will make possible to use formatCurrentFile and similar functions not only from Beautifier code. Change-Id: Ic5ca668afe88f4e9376d49e6bd3594807172b0dd Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -41,7 +41,7 @@ publication by Neil Fraser: http://neil.fraser.name/writing/diff/
|
||||
#include <QCoreApplication>
|
||||
#include <QFutureInterfaceBase>
|
||||
|
||||
namespace DiffEditor {
|
||||
namespace Utils {
|
||||
|
||||
static int commonPrefix(const QString &text1, const QString &text2)
|
||||
{
|
||||
@@ -1555,4 +1555,4 @@ QList<Diff> Differ::cleanupSemanticsLossless(const QList<Diff> &diffList)
|
||||
return newDiffList;
|
||||
}
|
||||
|
||||
} // namespace DiffEditor
|
||||
} // namespace Utils
|
@@ -25,7 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "diffeditor_global.h"
|
||||
#include "utils_global.h"
|
||||
#include <QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@@ -34,9 +34,9 @@ class QMap;
|
||||
class QFutureInterfaceBase;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace DiffEditor {
|
||||
namespace Utils {
|
||||
|
||||
class DIFFEDITOR_EXPORT Diff
|
||||
class QTCREATOR_UTILS_EXPORT Diff
|
||||
{
|
||||
public:
|
||||
enum Command {
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
static QString commandString(Command com);
|
||||
};
|
||||
|
||||
class DIFFEDITOR_EXPORT Differ
|
||||
class QTCREATOR_UTILS_EXPORT Differ
|
||||
{
|
||||
public:
|
||||
enum DiffMode
|
||||
@@ -114,4 +114,4 @@ private:
|
||||
QFutureInterfaceBase *m_jobController = nullptr;
|
||||
};
|
||||
|
||||
} // namespace DiffEditor
|
||||
} // namespace Utils
|
@@ -121,7 +121,8 @@ SOURCES += \
|
||||
$$PWD/url.cpp \
|
||||
$$PWD/filecrumblabel.cpp \
|
||||
$$PWD/fixedsizeclicklabel.cpp \
|
||||
$$PWD/removefiledialog.cpp
|
||||
$$PWD/removefiledialog.cpp \
|
||||
$$PWD/differ.cpp
|
||||
|
||||
win32:SOURCES += $$PWD/consoleprocess_win.cpp
|
||||
else:SOURCES += $$PWD/consoleprocess_unix.cpp
|
||||
@@ -258,7 +259,8 @@ HEADERS += \
|
||||
$$PWD/linecolumn.h \
|
||||
$$PWD/link.h \
|
||||
$$PWD/fixedsizeclicklabel.h \
|
||||
$$PWD/removefiledialog.h
|
||||
$$PWD/removefiledialog.h \
|
||||
$$PWD/differ.h
|
||||
|
||||
FORMS += $$PWD/filewizardpage.ui \
|
||||
$$PWD/newclasswidget.ui \
|
||||
|
@@ -75,6 +75,8 @@ Project {
|
||||
"detailsbutton.h",
|
||||
"detailswidget.cpp",
|
||||
"detailswidget.h",
|
||||
"differ.cpp",
|
||||
"differ.h",
|
||||
"dropsupport.cpp",
|
||||
"dropsupport.h",
|
||||
"elfreader.cpp",
|
||||
|
@@ -44,12 +44,15 @@
|
||||
#include <cppeditor/cppeditorconstants.h>
|
||||
#include <projectexplorer/projecttree.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <texteditor/formattexteditor.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
|
||||
using namespace TextEditor;
|
||||
|
||||
namespace Beautifier {
|
||||
namespace Internal {
|
||||
namespace ArtisticStyle {
|
||||
@@ -101,7 +104,7 @@ void ArtisticStyle::formatFile()
|
||||
BeautifierPlugin::showError(BeautifierPlugin::msgCannotGetConfigurationFile(
|
||||
tr(Constants::ArtisticStyle::DISPLAY_NAME)));
|
||||
} else {
|
||||
BeautifierPlugin::formatCurrentFile(command(cfgFileName));
|
||||
formatCurrentFile(command(cfgFileName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -46,7 +46,7 @@ public:
|
||||
bool initialize() override;
|
||||
QString id() const override;
|
||||
void updateActions(Core::IEditor *editor) override;
|
||||
Command command() const override;
|
||||
TextEditor::Command command() const override;
|
||||
bool isApplicable(const Core::IDocument *document) const override;
|
||||
|
||||
private:
|
||||
@@ -54,7 +54,7 @@ private:
|
||||
QAction *m_formatFile = nullptr;
|
||||
ArtisticStyleSettings *m_settings;
|
||||
QString configurationFile() const;
|
||||
Command command(const QString &cfgFile) const;
|
||||
TextEditor::Command command(const QString &cfgFile) const;
|
||||
};
|
||||
|
||||
} // namespace ArtisticStyle
|
||||
|
@@ -5,7 +5,6 @@ HEADERS += \
|
||||
beautifierabstracttool.h \
|
||||
beautifierconstants.h \
|
||||
beautifierplugin.h \
|
||||
command.h \
|
||||
configurationdialog.h \
|
||||
configurationeditor.h \
|
||||
configurationpanel.h \
|
||||
@@ -27,7 +26,6 @@ HEADERS += \
|
||||
SOURCES += \
|
||||
abstractsettings.cpp \
|
||||
beautifierplugin.cpp \
|
||||
command.cpp \
|
||||
configurationdialog.cpp \
|
||||
configurationeditor.cpp \
|
||||
configurationpanel.cpp \
|
||||
|
@@ -20,8 +20,6 @@ QtcPlugin {
|
||||
"beautifierconstants.h",
|
||||
"beautifierplugin.cpp",
|
||||
"beautifierplugin.h",
|
||||
"command.cpp",
|
||||
"command.h",
|
||||
"configurationdialog.cpp",
|
||||
"configurationdialog.h",
|
||||
"configurationdialog.ui",
|
||||
|
@@ -25,7 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "command.h"
|
||||
#include <texteditor/command.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
*
|
||||
* @note The received command may be invalid.
|
||||
*/
|
||||
virtual Command command() const = 0;
|
||||
virtual TextEditor::Command command() const = 0;
|
||||
|
||||
virtual bool isApplicable(const Core::IDocument *document) const = 0;
|
||||
};
|
||||
|
@@ -42,9 +42,9 @@
|
||||
#include <coreplugin/editormanager/ieditor.h>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <cppeditor/cppeditorconstants.h>
|
||||
#include <diffeditor/differ.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projecttree.h>
|
||||
#include <texteditor/formattexteditor.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/textdocumentlayout.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
@@ -72,127 +72,6 @@ using namespace TextEditor;
|
||||
namespace Beautifier {
|
||||
namespace Internal {
|
||||
|
||||
struct FormatTask
|
||||
{
|
||||
FormatTask(QPlainTextEdit *_editor, const QString &_filePath, const QString &_sourceData,
|
||||
const Command &_command, int _startPos = -1, int _endPos = 0) :
|
||||
editor(_editor),
|
||||
filePath(_filePath),
|
||||
sourceData(_sourceData),
|
||||
command(_command),
|
||||
startPos(_startPos),
|
||||
endPos(_endPos) {}
|
||||
|
||||
QPointer<QPlainTextEdit> editor;
|
||||
QString filePath;
|
||||
QString sourceData;
|
||||
Command command;
|
||||
int startPos = -1;
|
||||
int endPos = 0;
|
||||
QString formattedData;
|
||||
QString error;
|
||||
};
|
||||
|
||||
FormatTask format(FormatTask task)
|
||||
{
|
||||
task.error.clear();
|
||||
task.formattedData.clear();
|
||||
|
||||
const QString executable = task.command.executable();
|
||||
if (executable.isEmpty())
|
||||
return task;
|
||||
|
||||
switch (task.command.processing()) {
|
||||
case Command::FileProcessing: {
|
||||
// Save text to temporary file
|
||||
const QFileInfo fi(task.filePath);
|
||||
Utils::TempFileSaver sourceFile(Utils::TemporaryDirectory::masterDirectoryPath()
|
||||
+ "/qtc_beautifier_XXXXXXXX."
|
||||
+ fi.suffix());
|
||||
sourceFile.setAutoRemove(true);
|
||||
sourceFile.write(task.sourceData.toUtf8());
|
||||
if (!sourceFile.finalize()) {
|
||||
task.error = BeautifierPlugin::tr("Cannot create temporary file \"%1\": %2.")
|
||||
.arg(sourceFile.fileName()).arg(sourceFile.errorString());
|
||||
return task;
|
||||
}
|
||||
|
||||
// Format temporary file
|
||||
QStringList options = task.command.options();
|
||||
options.replaceInStrings(QLatin1String("%file"), sourceFile.fileName());
|
||||
Utils::SynchronousProcess process;
|
||||
process.setTimeoutS(5);
|
||||
Utils::SynchronousProcessResponse response = process.runBlocking(executable, options);
|
||||
if (response.result != Utils::SynchronousProcessResponse::Finished) {
|
||||
task.error = BeautifierPlugin::tr("Failed to format: %1.").arg(response.exitMessage(executable, 5));
|
||||
return task;
|
||||
}
|
||||
const QString output = response.stdErr();
|
||||
if (!output.isEmpty())
|
||||
task.error = executable + QLatin1String(": ") + output;
|
||||
|
||||
// Read text back
|
||||
Utils::FileReader reader;
|
||||
if (!reader.fetch(sourceFile.fileName(), QIODevice::Text)) {
|
||||
task.error = BeautifierPlugin::tr("Cannot read file \"%1\": %2.")
|
||||
.arg(sourceFile.fileName()).arg(reader.errorString());
|
||||
return task;
|
||||
}
|
||||
task.formattedData = QString::fromUtf8(reader.data());
|
||||
}
|
||||
return task;
|
||||
|
||||
case Command::PipeProcessing: {
|
||||
QProcess process;
|
||||
QStringList options = task.command.options();
|
||||
options.replaceInStrings("%filename", QFileInfo(task.filePath).fileName());
|
||||
options.replaceInStrings("%file", task.filePath);
|
||||
process.start(executable, options);
|
||||
if (!process.waitForStarted(3000)) {
|
||||
task.error = BeautifierPlugin::tr("Cannot call %1 or some other error occurred.")
|
||||
.arg(executable);
|
||||
return task;
|
||||
}
|
||||
process.write(task.sourceData.toUtf8());
|
||||
process.closeWriteChannel();
|
||||
if (!process.waitForFinished(5000) && process.state() == QProcess::Running) {
|
||||
process.kill();
|
||||
task.error = BeautifierPlugin::tr("Cannot call %1 or some other error occurred. Timeout "
|
||||
"reached while formatting file %2.")
|
||||
.arg(executable).arg(task.filePath);
|
||||
return task;
|
||||
}
|
||||
const QByteArray errorText = process.readAllStandardError();
|
||||
if (!errorText.isEmpty()) {
|
||||
task.error = QString::fromLatin1("%1: %2").arg(executable)
|
||||
.arg(QString::fromUtf8(errorText));
|
||||
return task;
|
||||
}
|
||||
|
||||
task.formattedData = QString::fromUtf8(process.readAllStandardOutput());
|
||||
|
||||
if (task.command.pipeAddsNewline() && task.formattedData.endsWith('\n')) {
|
||||
task.formattedData.chop(1);
|
||||
if (task.formattedData.endsWith('\r'))
|
||||
task.formattedData.chop(1);
|
||||
}
|
||||
if (task.command.returnsCRLF())
|
||||
task.formattedData.replace("\r\n", "\n");
|
||||
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
QString sourceData(TextEditorWidget *editor, int startPos, int endPos)
|
||||
{
|
||||
return (startPos < 0)
|
||||
? editor->toPlainText()
|
||||
: Utils::Text::textAt(editor->textCursor(), startPos, (endPos - startPos));
|
||||
}
|
||||
|
||||
bool isAutoFormatApplicable(const Core::IDocument *document,
|
||||
const QList<Utils::MimeType> &allowedMimeTypes)
|
||||
{
|
||||
@@ -215,17 +94,9 @@ public:
|
||||
|
||||
void updateActions(Core::IEditor *editor = nullptr);
|
||||
|
||||
void formatEditor(TextEditor::TextEditorWidget *editor, const Command &command,
|
||||
int startPos = -1, int endPos = 0);
|
||||
void formatEditorAsync(TextEditor::TextEditorWidget *editor, const Command &command,
|
||||
int startPos = -1, int endPos = 0);
|
||||
void checkAndApplyTask(const FormatTask &task);
|
||||
void updateEditorText(QPlainTextEdit *editor, const QString &text);
|
||||
|
||||
void autoFormatOnSave(Core::IDocument *document);
|
||||
|
||||
QSharedPointer<GeneralSettings> m_generalSettings;
|
||||
QHash<QObject*, QMetaObject::Connection> m_autoFormatConnections;
|
||||
|
||||
ArtisticStyle::ArtisticStyle artisticStyleBeautifier;
|
||||
ClangFormat::ClangFormat clangFormatBeautifier;
|
||||
@@ -304,221 +175,17 @@ void BeautifierPluginPrivate::autoFormatOnSave(Core::IDocument *document)
|
||||
if (tool != m_tools.constEnd()) {
|
||||
if (!(*tool)->isApplicable(document))
|
||||
return;
|
||||
const Command command = (*tool)->command();
|
||||
const TextEditor::Command command = (*tool)->command();
|
||||
if (!command.isValid())
|
||||
return;
|
||||
const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForDocument(document);
|
||||
if (editors.isEmpty())
|
||||
return;
|
||||
if (TextEditorWidget* widget = qobject_cast<TextEditorWidget *>(editors.first()->widget()))
|
||||
formatEditor(widget, command);
|
||||
TextEditor::formatEditor(widget, command);
|
||||
}
|
||||
}
|
||||
|
||||
void BeautifierPlugin::formatCurrentFile(const Command &command, int startPos, int endPos)
|
||||
{
|
||||
QTC_ASSERT(dd, return);
|
||||
if (TextEditorWidget *editor = TextEditorWidget::currentTextEditorWidget())
|
||||
dd->formatEditorAsync(editor, command, startPos, endPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the text of @a editor using @a command. @a startPos and @a endPos specifies the range of
|
||||
* the editor's text that will be formatted. If @a startPos is negative the editor's entire text is
|
||||
* formatted.
|
||||
*
|
||||
* @pre @a endPos must be greater than or equal to @a startPos
|
||||
*/
|
||||
void BeautifierPluginPrivate::formatEditor(TextEditorWidget *editor, const Command &command,
|
||||
int startPos, int endPos)
|
||||
{
|
||||
QTC_ASSERT(startPos <= endPos, return);
|
||||
|
||||
const QString sd = sourceData(editor, startPos, endPos);
|
||||
if (sd.isEmpty())
|
||||
return;
|
||||
checkAndApplyTask(format(FormatTask(editor, editor->textDocument()->filePath().toString(), sd,
|
||||
command, startPos, endPos)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves like formatEditor except that the formatting is done asynchronously.
|
||||
*/
|
||||
void BeautifierPluginPrivate::formatEditorAsync(TextEditorWidget *editor, const Command &command,
|
||||
int startPos, int endPos)
|
||||
{
|
||||
QTC_ASSERT(startPos <= endPos, return);
|
||||
|
||||
const QString sd = sourceData(editor, startPos, endPos);
|
||||
if (sd.isEmpty())
|
||||
return;
|
||||
|
||||
QFutureWatcher<FormatTask> *watcher = new QFutureWatcher<FormatTask>;
|
||||
const TextDocument *doc = editor->textDocument();
|
||||
connect(doc, &TextDocument::contentsChanged, watcher, &QFutureWatcher<FormatTask>::cancel);
|
||||
connect(watcher, &QFutureWatcherBase::finished, [this, watcher] {
|
||||
if (watcher->isCanceled())
|
||||
BeautifierPlugin::showError(BeautifierPlugin::tr("File was modified."));
|
||||
else
|
||||
checkAndApplyTask(watcher->result());
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(Utils::runAsync(&format, FormatTask(editor, doc->filePath().toString(), sd,
|
||||
command, startPos, endPos)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the state of @a task and if the formatting was successful calls updateEditorText() with
|
||||
* the respective members of @a task.
|
||||
*/
|
||||
void BeautifierPluginPrivate::checkAndApplyTask(const FormatTask &task)
|
||||
{
|
||||
if (!task.error.isEmpty()) {
|
||||
BeautifierPlugin::showError(task.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (task.formattedData.isEmpty()) {
|
||||
BeautifierPlugin::showError(BeautifierPlugin::tr("Could not format file %1.").arg(task.filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
QPlainTextEdit *textEditor = task.editor;
|
||||
if (!textEditor) {
|
||||
BeautifierPlugin::showError(BeautifierPlugin::tr("File %1 was closed.").arg(task.filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString formattedData = (task.startPos < 0)
|
||||
? task.formattedData
|
||||
: QString(textEditor->toPlainText()).replace(
|
||||
task.startPos, (task.endPos - task.startPos), task.formattedData);
|
||||
|
||||
updateEditorText(textEditor, formattedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text of @a editor to @a text. Instead of replacing the entire text, however, only the
|
||||
* actually changed parts are updated while preserving the cursor position, the folded
|
||||
* blocks, and the scroll bar position.
|
||||
*/
|
||||
void BeautifierPluginPrivate::updateEditorText(QPlainTextEdit *editor, const QString &text)
|
||||
{
|
||||
const QString editorText = editor->toPlainText();
|
||||
if (editorText == text)
|
||||
return;
|
||||
|
||||
// Calculate diff
|
||||
DiffEditor::Differ differ;
|
||||
const QList<DiffEditor::Diff> diff = differ.diff(editorText, text);
|
||||
|
||||
// Since QTextCursor does not work properly with folded blocks, all blocks must be unfolded.
|
||||
// To restore the current state at the end, keep track of which block is folded.
|
||||
QList<int> foldedBlocks;
|
||||
QTextBlock block = editor->document()->firstBlock();
|
||||
while (block.isValid()) {
|
||||
if (const TextBlockUserData *userdata = static_cast<TextBlockUserData *>(block.userData())) {
|
||||
if (userdata->folded()) {
|
||||
foldedBlocks << block.blockNumber();
|
||||
TextDocumentLayout::doFoldOrUnfold(block, true);
|
||||
}
|
||||
}
|
||||
block = block.next();
|
||||
}
|
||||
editor->update();
|
||||
|
||||
// Save the current viewport position of the cursor to ensure the same vertical position after
|
||||
// the formatted text has set to the editor.
|
||||
int absoluteVerticalCursorOffset = editor->cursorRect().y();
|
||||
|
||||
// Update changed lines and keep track of the cursor position
|
||||
QTextCursor cursor = editor->textCursor();
|
||||
int charactersInfrontOfCursor = cursor.position();
|
||||
int newCursorPos = charactersInfrontOfCursor;
|
||||
cursor.beginEditBlock();
|
||||
cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
|
||||
for (const DiffEditor::Diff &d : diff) {
|
||||
switch (d.command) {
|
||||
case DiffEditor::Diff::Insert:
|
||||
{
|
||||
// Adjust cursor position if we do work in front of the cursor.
|
||||
if (charactersInfrontOfCursor > 0) {
|
||||
const int size = d.text.size();
|
||||
charactersInfrontOfCursor += size;
|
||||
newCursorPos += size;
|
||||
}
|
||||
// Adjust folded blocks, if a new block is added.
|
||||
if (d.text.contains('\n')) {
|
||||
const int newLineCount = d.text.count('\n');
|
||||
const int number = cursor.blockNumber();
|
||||
const int total = foldedBlocks.size();
|
||||
for (int i = 0; i < total; ++i) {
|
||||
if (foldedBlocks.at(i) > number)
|
||||
foldedBlocks[i] += newLineCount;
|
||||
}
|
||||
}
|
||||
cursor.insertText(d.text);
|
||||
break;
|
||||
}
|
||||
|
||||
case DiffEditor::Diff::Delete:
|
||||
{
|
||||
// Adjust cursor position if we do work in front of the cursor.
|
||||
if (charactersInfrontOfCursor > 0) {
|
||||
const int size = d.text.size();
|
||||
charactersInfrontOfCursor -= size;
|
||||
newCursorPos -= size;
|
||||
// Cursor was inside the deleted text, so adjust the new cursor position
|
||||
if (charactersInfrontOfCursor < 0)
|
||||
newCursorPos -= charactersInfrontOfCursor;
|
||||
}
|
||||
// Adjust folded blocks, if at least one block is being deleted.
|
||||
if (d.text.contains('\n')) {
|
||||
const int newLineCount = d.text.count('\n');
|
||||
const int number = cursor.blockNumber();
|
||||
for (int i = 0, total = foldedBlocks.size(); i < total; ++i) {
|
||||
if (foldedBlocks.at(i) > number) {
|
||||
foldedBlocks[i] -= newLineCount;
|
||||
if (foldedBlocks[i] < number) {
|
||||
foldedBlocks.removeAt(i);
|
||||
--i;
|
||||
--total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, d.text.size());
|
||||
cursor.removeSelectedText();
|
||||
break;
|
||||
}
|
||||
|
||||
case DiffEditor::Diff::Equal:
|
||||
// Adjust cursor position
|
||||
charactersInfrontOfCursor -= d.text.size();
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, d.text.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
cursor.endEditBlock();
|
||||
cursor.setPosition(newCursorPos);
|
||||
editor->setTextCursor(cursor);
|
||||
|
||||
// Adjust vertical scrollbar
|
||||
absoluteVerticalCursorOffset = editor->cursorRect().y() - absoluteVerticalCursorOffset;
|
||||
const double fontHeight = QFontMetrics(editor->document()->defaultFont()).height();
|
||||
editor->verticalScrollBar()->setValue(editor->verticalScrollBar()->value()
|
||||
+ absoluteVerticalCursorOffset / fontHeight);
|
||||
// Restore folded blocks
|
||||
const QTextDocument *doc = editor->document();
|
||||
for (int blockId : foldedBlocks) {
|
||||
const QTextBlock block = doc->findBlockByNumber(qMax(0, blockId));
|
||||
if (block.isValid())
|
||||
TextDocumentLayout::doFoldOrUnfold(block, false);
|
||||
}
|
||||
|
||||
editor->document()->setModified(true);
|
||||
}
|
||||
|
||||
void BeautifierPlugin::showError(const QString &error)
|
||||
{
|
||||
Core::MessageManager::write(tr("Error in Beautifier: %1").arg(error.trimmed()));
|
||||
|
@@ -25,9 +25,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "command.h"
|
||||
|
||||
#include <extensionsystem/iplugin.h>
|
||||
#include <texteditor/command.h>
|
||||
|
||||
namespace Beautifier {
|
||||
namespace Internal {
|
||||
@@ -38,8 +37,6 @@ class BeautifierPlugin : public ExtensionSystem::IPlugin
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Beautifier.json")
|
||||
|
||||
public:
|
||||
static void formatCurrentFile(const Command &command, int startPos = -1, int endPos = 0);
|
||||
|
||||
static QString msgCannotGetConfigurationFile(const QString &command);
|
||||
static QString msgFormatCurrentFile();
|
||||
static QString msgFormatSelectedText();
|
||||
|
@@ -42,6 +42,7 @@
|
||||
#include <coreplugin/editormanager/ieditor.h>
|
||||
#include <coreplugin/idocument.h>
|
||||
#include <cppeditor/cppeditorconstants.h>
|
||||
#include <texteditor/formattexteditor.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fileutils.h>
|
||||
@@ -50,6 +51,8 @@
|
||||
#include <QMenu>
|
||||
#include <QTextBlock>
|
||||
|
||||
using namespace TextEditor;
|
||||
|
||||
namespace Beautifier {
|
||||
namespace Internal {
|
||||
namespace ClangFormat {
|
||||
@@ -114,13 +117,12 @@ void ClangFormat::updateActions(Core::IEditor *editor)
|
||||
|
||||
void ClangFormat::formatFile()
|
||||
{
|
||||
BeautifierPlugin::formatCurrentFile(command());
|
||||
formatCurrentFile(command());
|
||||
}
|
||||
|
||||
void ClangFormat::formatAtCursor()
|
||||
{
|
||||
const TextEditor::TextEditorWidget *widget
|
||||
= TextEditor::TextEditorWidget::currentTextEditorWidget();
|
||||
const TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget();
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
@@ -128,7 +130,7 @@ void ClangFormat::formatAtCursor()
|
||||
if (tc.hasSelection()) {
|
||||
const int offset = tc.selectionStart();
|
||||
const int length = tc.selectionEnd() - offset;
|
||||
BeautifierPlugin::formatCurrentFile(command(offset, length));
|
||||
formatCurrentFile(command(offset, length));
|
||||
} else {
|
||||
// Pretend that the current line was selected.
|
||||
// Note that clang-format will extend the range to the next bigger
|
||||
@@ -136,13 +138,13 @@ void ClangFormat::formatAtCursor()
|
||||
const QTextBlock block = tc.block();
|
||||
const int offset = block.position();
|
||||
const int length = block.length();
|
||||
BeautifierPlugin::formatCurrentFile(command(offset, length));
|
||||
formatCurrentFile(command(offset, length));
|
||||
}
|
||||
}
|
||||
|
||||
void ClangFormat::disableFormattingSelectedText()
|
||||
{
|
||||
TextEditor::TextEditorWidget *widget = TextEditor::TextEditorWidget::currentTextEditorWidget();
|
||||
TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget();
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
@@ -172,8 +174,7 @@ void ClangFormat::disableFormattingSelectedText()
|
||||
// The indentation of these markers might be undesired, so reformat.
|
||||
// This is not optimal because two undo steps will be needed to remove the markers.
|
||||
const int reformatTextLength = insertCursor.position() - selectionStartBlock.position();
|
||||
BeautifierPlugin::formatCurrentFile(command(selectionStartBlock.position(),
|
||||
reformatTextLength));
|
||||
formatCurrentFile(command(selectionStartBlock.position(), reformatTextLength));
|
||||
}
|
||||
|
||||
Command ClangFormat::command() const
|
||||
|
@@ -46,7 +46,7 @@ public:
|
||||
QString id() const override;
|
||||
bool initialize() override;
|
||||
void updateActions(Core::IEditor *editor) override;
|
||||
Command command() const override;
|
||||
TextEditor::Command command() const override;
|
||||
bool isApplicable(const Core::IDocument *document) const override;
|
||||
|
||||
private:
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
QAction *m_formatRange = nullptr;
|
||||
QAction *m_disableFormattingSelectedText = nullptr;
|
||||
ClangFormatSettings *m_settings;
|
||||
Command command(int offset, int length) const;
|
||||
TextEditor::Command command(int offset, int length) const;
|
||||
};
|
||||
|
||||
} // namespace ClangFormat
|
||||
|
@@ -44,12 +44,15 @@
|
||||
#include <cppeditor/cppeditorconstants.h>
|
||||
#include <projectexplorer/projecttree.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <texteditor/formattexteditor.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/fileutils.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
|
||||
using namespace TextEditor;
|
||||
|
||||
namespace Beautifier {
|
||||
namespace Internal {
|
||||
namespace Uncrustify {
|
||||
@@ -111,7 +114,7 @@ void Uncrustify::formatFile()
|
||||
BeautifierPlugin::showError(BeautifierPlugin::msgCannotGetConfigurationFile(
|
||||
tr(Constants::Uncrustify::DISPLAY_NAME)));
|
||||
} else {
|
||||
BeautifierPlugin::formatCurrentFile(command(cfgFileName));
|
||||
formatCurrentFile(command(cfgFileName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +127,7 @@ void Uncrustify::formatSelectedText()
|
||||
return;
|
||||
}
|
||||
|
||||
const TextEditor::TextEditorWidget *widget
|
||||
= TextEditor::TextEditorWidget::currentTextEditorWidget();
|
||||
const TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget();
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
@@ -141,7 +143,7 @@ void Uncrustify::formatSelectedText()
|
||||
if (tc.positionInBlock() > 0)
|
||||
tc.movePosition(QTextCursor::EndOfLine);
|
||||
const int endPos = tc.position();
|
||||
BeautifierPlugin::formatCurrentFile(command(cfgFileName, true), startPos, endPos);
|
||||
formatCurrentFile(command(cfgFileName, true), startPos, endPos);
|
||||
} else if (m_settings->formatEntireFileFallback()) {
|
||||
formatFile();
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ public:
|
||||
bool initialize() override;
|
||||
QString id() const override;
|
||||
void updateActions(Core::IEditor *editor) override;
|
||||
Command command() const override;
|
||||
TextEditor::Command command() const override;
|
||||
bool isApplicable(const Core::IDocument *document) const override;
|
||||
|
||||
private:
|
||||
@@ -55,7 +55,7 @@ private:
|
||||
QAction *m_formatRange = nullptr;
|
||||
UncrustifySettings *m_settings;
|
||||
QString configurationFile() const;
|
||||
Command command(const QString &cfgFile, bool fragment = false) const;
|
||||
TextEditor::Command command(const QString &cfgFile, bool fragment = false) const;
|
||||
};
|
||||
|
||||
} // namespace Uncrustify
|
||||
|
@@ -11,7 +11,6 @@ HEADERS += \
|
||||
diffeditorfactory.h \
|
||||
diffeditorplugin.h \
|
||||
diffeditorwidgetcontroller.h \
|
||||
differ.h \
|
||||
diffutils.h \
|
||||
diffview.h \
|
||||
selectabletexteditorwidget.h \
|
||||
@@ -27,7 +26,6 @@ SOURCES += \
|
||||
diffeditorfactory.cpp \
|
||||
diffeditorplugin.cpp \
|
||||
diffeditorwidgetcontroller.cpp \
|
||||
differ.cpp \
|
||||
diffutils.cpp \
|
||||
diffview.cpp \
|
||||
selectabletexteditorwidget.cpp \
|
||||
|
@@ -32,8 +32,6 @@ QtcPlugin {
|
||||
"diffeditorplugin.h",
|
||||
"diffeditorwidgetcontroller.cpp",
|
||||
"diffeditorwidgetcontroller.h",
|
||||
"differ.cpp",
|
||||
"differ.h",
|
||||
"diffutils.cpp",
|
||||
"diffutils.h",
|
||||
"diffview.cpp",
|
||||
|
@@ -29,7 +29,6 @@
|
||||
#include "diffeditorcontroller.h"
|
||||
#include "diffeditordocument.h"
|
||||
#include "diffeditorfactory.h"
|
||||
#include "differ.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QFileDialog>
|
||||
@@ -50,10 +49,12 @@
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/differ.h>
|
||||
#include <utils/mapreduce.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
using namespace Core;
|
||||
using namespace Utils;
|
||||
|
||||
namespace DiffEditor {
|
||||
namespace Internal {
|
||||
|
@@ -24,15 +24,17 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include "diffutils.h"
|
||||
#include "differ.h"
|
||||
|
||||
#include "texteditor/fontsettings.h"
|
||||
#include <texteditor/fontsettings.h>
|
||||
#include <utils/differ.h>
|
||||
|
||||
#include <QFutureInterfaceBase>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace DiffEditor {
|
||||
|
||||
static QList<TextLineData> assemblyRows(const QList<TextLineData> &lines,
|
||||
@@ -103,7 +105,7 @@ static void handleDifference(const QString &text,
|
||||
* The number of equalities on both lists must be the same.
|
||||
*/
|
||||
ChunkData DiffUtils::calculateOriginalData(const QList<Diff> &leftDiffList,
|
||||
const QList<Diff> &rightDiffList)
|
||||
const QList<Diff> &rightDiffList)
|
||||
{
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
@@ -36,9 +36,9 @@ QT_END_NAMESPACE
|
||||
|
||||
namespace TextEditor { class FontSettings; }
|
||||
|
||||
namespace DiffEditor {
|
||||
namespace Utils { class Diff; }
|
||||
|
||||
class Diff;
|
||||
namespace DiffEditor {
|
||||
|
||||
class DIFFEDITOR_EXPORT DiffFileInfo {
|
||||
public:
|
||||
@@ -128,8 +128,8 @@ public:
|
||||
GitFormat = AddLevel | 0x2, // Add line 'diff ..' as git does
|
||||
};
|
||||
|
||||
static ChunkData calculateOriginalData(const QList<Diff> &leftDiffList,
|
||||
const QList<Diff> &rightDiffList);
|
||||
static ChunkData calculateOriginalData(const QList<Utils::Diff> &leftDiffList,
|
||||
const QList<Utils::Diff> &rightDiffList);
|
||||
static FileData calculateContextData(const ChunkData &originalData,
|
||||
int contextLineCount,
|
||||
int joinChunkThreshold = 1);
|
||||
|
@@ -25,8 +25,7 @@
|
||||
|
||||
#include "command.h"
|
||||
|
||||
namespace Beautifier {
|
||||
namespace Internal {
|
||||
namespace TextEditor {
|
||||
|
||||
bool Command::isValid() const
|
||||
{
|
||||
@@ -83,5 +82,4 @@ void Command::setReturnsCRLF(bool returnsCRLF)
|
||||
m_returnsCRLF = returnsCRLF;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Beautifier
|
||||
} // namespace TextEditor
|
@@ -25,13 +25,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "texteditor_global.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace Beautifier {
|
||||
namespace Internal {
|
||||
namespace TextEditor {
|
||||
|
||||
class Command
|
||||
class TEXTEDITOR_EXPORT Command
|
||||
{
|
||||
public:
|
||||
enum Processing {
|
||||
@@ -64,5 +65,4 @@ private:
|
||||
bool m_returnsCRLF = false;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Beautifier
|
||||
} // namespace TextEditor
|
364
src/plugins/texteditor/formattexteditor.cpp
Normal file
364
src/plugins/texteditor/formattexteditor.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "formattexteditor.h"
|
||||
|
||||
#include "textdocument.h"
|
||||
#include "textdocumentlayout.h"
|
||||
#include "texteditor.h"
|
||||
|
||||
#include <coreplugin/messagemanager.h>
|
||||
|
||||
#include <utils/differ.h>
|
||||
#include <utils/runextensions.h>
|
||||
#include <utils/synchronousprocess.h>
|
||||
#include <utils/temporarydirectory.h>
|
||||
#include <utils/textutils.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QFutureWatcher>
|
||||
#include <QScrollBar>
|
||||
#include <QTextBlock>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace TextEditor {
|
||||
|
||||
void formatCurrentFile(const Command &command, int startPos, int endPos)
|
||||
{
|
||||
if (TextEditorWidget *editor = TextEditorWidget::currentTextEditorWidget())
|
||||
formatEditorAsync(editor, command, startPos, endPos);
|
||||
}
|
||||
|
||||
static QString sourceData(TextEditorWidget *editor, int startPos, int endPos)
|
||||
{
|
||||
return (startPos < 0)
|
||||
? editor->toPlainText()
|
||||
: Utils::Text::textAt(editor->textCursor(), startPos, (endPos - startPos));
|
||||
}
|
||||
|
||||
static FormatTask format(FormatTask task)
|
||||
{
|
||||
task.error.clear();
|
||||
task.formattedData.clear();
|
||||
|
||||
const QString executable = task.command.executable();
|
||||
if (executable.isEmpty())
|
||||
return task;
|
||||
|
||||
switch (task.command.processing()) {
|
||||
case Command::FileProcessing: {
|
||||
// Save text to temporary file
|
||||
const QFileInfo fi(task.filePath);
|
||||
Utils::TempFileSaver sourceFile(Utils::TemporaryDirectory::masterDirectoryPath()
|
||||
+ "/qtc_beautifier_XXXXXXXX."
|
||||
+ fi.suffix());
|
||||
sourceFile.setAutoRemove(true);
|
||||
sourceFile.write(task.sourceData.toUtf8());
|
||||
if (!sourceFile.finalize()) {
|
||||
task.error = QString(QT_TRANSLATE_NOOP("TextEditor",
|
||||
"Cannot create temporary file \"%1\": %2."))
|
||||
.arg(sourceFile.fileName(), sourceFile.errorString());
|
||||
return task;
|
||||
}
|
||||
|
||||
// Format temporary file
|
||||
QStringList options = task.command.options();
|
||||
options.replaceInStrings(QLatin1String("%file"), sourceFile.fileName());
|
||||
Utils::SynchronousProcess process;
|
||||
process.setTimeoutS(5);
|
||||
Utils::SynchronousProcessResponse response = process.runBlocking(executable, options);
|
||||
if (response.result != Utils::SynchronousProcessResponse::Finished) {
|
||||
task.error = QString(QT_TRANSLATE_NOOP("TextEditor", "Failed to format: %1."))
|
||||
.arg(response.exitMessage(executable, 5));
|
||||
return task;
|
||||
}
|
||||
const QString output = response.stdErr();
|
||||
if (!output.isEmpty())
|
||||
task.error = executable + QLatin1String(": ") + output;
|
||||
|
||||
// Read text back
|
||||
Utils::FileReader reader;
|
||||
if (!reader.fetch(sourceFile.fileName(), QIODevice::Text)) {
|
||||
task.error = QString(QT_TRANSLATE_NOOP("TextEditor", "Cannot read file \"%1\": %2."))
|
||||
.arg(sourceFile.fileName(), reader.errorString());
|
||||
return task;
|
||||
}
|
||||
task.formattedData = QString::fromUtf8(reader.data());
|
||||
}
|
||||
return task;
|
||||
|
||||
case Command::PipeProcessing: {
|
||||
QProcess process;
|
||||
QStringList options = task.command.options();
|
||||
options.replaceInStrings("%filename", QFileInfo(task.filePath).fileName());
|
||||
options.replaceInStrings("%file", task.filePath);
|
||||
process.start(executable, options);
|
||||
if (!process.waitForStarted(3000)) {
|
||||
task.error = QString(QT_TRANSLATE_NOOP("TextEditor",
|
||||
"Cannot call %1 or some other error occurred."))
|
||||
.arg(executable);
|
||||
return task;
|
||||
}
|
||||
process.write(task.sourceData.toUtf8());
|
||||
process.closeWriteChannel();
|
||||
if (!process.waitForFinished(5000) && process.state() == QProcess::Running) {
|
||||
process.kill();
|
||||
task.error = QString(QT_TRANSLATE_NOOP("TextEditor",
|
||||
"Cannot call %1 or some other error occurred. Timeout "
|
||||
"reached while formatting file %2."))
|
||||
.arg(executable, task.filePath);
|
||||
return task;
|
||||
}
|
||||
const QByteArray errorText = process.readAllStandardError();
|
||||
if (!errorText.isEmpty()) {
|
||||
task.error = QString::fromLatin1("%1: %2").arg(executable,
|
||||
QString::fromUtf8(errorText));
|
||||
return task;
|
||||
}
|
||||
|
||||
task.formattedData = QString::fromUtf8(process.readAllStandardOutput());
|
||||
|
||||
if (task.command.pipeAddsNewline() && task.formattedData.endsWith('\n')) {
|
||||
task.formattedData.chop(1);
|
||||
if (task.formattedData.endsWith('\r'))
|
||||
task.formattedData.chop(1);
|
||||
}
|
||||
if (task.command.returnsCRLF())
|
||||
task.formattedData.replace("\r\n", "\n");
|
||||
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text of @a editor to @a text. Instead of replacing the entire text, however, only the
|
||||
* actually changed parts are updated while preserving the cursor position, the folded
|
||||
* blocks, and the scroll bar position.
|
||||
*/
|
||||
static void updateEditorText(QPlainTextEdit *editor, const QString &text)
|
||||
{
|
||||
const QString editorText = editor->toPlainText();
|
||||
if (editorText == text)
|
||||
return;
|
||||
|
||||
// Calculate diff
|
||||
Differ differ;
|
||||
const QList<Diff> diff = differ.diff(editorText, text);
|
||||
|
||||
// Since QTextCursor does not work properly with folded blocks, all blocks must be unfolded.
|
||||
// To restore the current state at the end, keep track of which block is folded.
|
||||
QList<int> foldedBlocks;
|
||||
QTextBlock block = editor->document()->firstBlock();
|
||||
while (block.isValid()) {
|
||||
if (const TextBlockUserData *userdata = static_cast<TextBlockUserData *>(block.userData())) {
|
||||
if (userdata->folded()) {
|
||||
foldedBlocks << block.blockNumber();
|
||||
TextDocumentLayout::doFoldOrUnfold(block, true);
|
||||
}
|
||||
}
|
||||
block = block.next();
|
||||
}
|
||||
editor->update();
|
||||
|
||||
// Save the current viewport position of the cursor to ensure the same vertical position after
|
||||
// the formatted text has set to the editor.
|
||||
int absoluteVerticalCursorOffset = editor->cursorRect().y();
|
||||
|
||||
// Update changed lines and keep track of the cursor position
|
||||
QTextCursor cursor = editor->textCursor();
|
||||
int charactersInfrontOfCursor = cursor.position();
|
||||
int newCursorPos = charactersInfrontOfCursor;
|
||||
cursor.beginEditBlock();
|
||||
cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
|
||||
for (const Diff &d : diff) {
|
||||
switch (d.command) {
|
||||
case Diff::Insert:
|
||||
{
|
||||
// Adjust cursor position if we do work in front of the cursor.
|
||||
if (charactersInfrontOfCursor > 0) {
|
||||
const int size = d.text.size();
|
||||
charactersInfrontOfCursor += size;
|
||||
newCursorPos += size;
|
||||
}
|
||||
// Adjust folded blocks, if a new block is added.
|
||||
if (d.text.contains('\n')) {
|
||||
const int newLineCount = d.text.count('\n');
|
||||
const int number = cursor.blockNumber();
|
||||
const int total = foldedBlocks.size();
|
||||
for (int i = 0; i < total; ++i) {
|
||||
if (foldedBlocks.at(i) > number)
|
||||
foldedBlocks[i] += newLineCount;
|
||||
}
|
||||
}
|
||||
cursor.insertText(d.text);
|
||||
break;
|
||||
}
|
||||
|
||||
case Diff::Delete:
|
||||
{
|
||||
// Adjust cursor position if we do work in front of the cursor.
|
||||
if (charactersInfrontOfCursor > 0) {
|
||||
const int size = d.text.size();
|
||||
charactersInfrontOfCursor -= size;
|
||||
newCursorPos -= size;
|
||||
// Cursor was inside the deleted text, so adjust the new cursor position
|
||||
if (charactersInfrontOfCursor < 0)
|
||||
newCursorPos -= charactersInfrontOfCursor;
|
||||
}
|
||||
// Adjust folded blocks, if at least one block is being deleted.
|
||||
if (d.text.contains('\n')) {
|
||||
const int newLineCount = d.text.count('\n');
|
||||
const int number = cursor.blockNumber();
|
||||
for (int i = 0, total = foldedBlocks.size(); i < total; ++i) {
|
||||
if (foldedBlocks.at(i) > number) {
|
||||
foldedBlocks[i] -= newLineCount;
|
||||
if (foldedBlocks[i] < number) {
|
||||
foldedBlocks.removeAt(i);
|
||||
--i;
|
||||
--total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, d.text.size());
|
||||
cursor.removeSelectedText();
|
||||
break;
|
||||
}
|
||||
|
||||
case Diff::Equal:
|
||||
// Adjust cursor position
|
||||
charactersInfrontOfCursor -= d.text.size();
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, d.text.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
cursor.endEditBlock();
|
||||
cursor.setPosition(newCursorPos);
|
||||
editor->setTextCursor(cursor);
|
||||
|
||||
// Adjust vertical scrollbar
|
||||
absoluteVerticalCursorOffset = editor->cursorRect().y() - absoluteVerticalCursorOffset;
|
||||
const double fontHeight = QFontMetrics(editor->document()->defaultFont()).height();
|
||||
editor->verticalScrollBar()->setValue(editor->verticalScrollBar()->value()
|
||||
+ absoluteVerticalCursorOffset / fontHeight);
|
||||
// Restore folded blocks
|
||||
const QTextDocument *doc = editor->document();
|
||||
for (int blockId : foldedBlocks) {
|
||||
const QTextBlock block = doc->findBlockByNumber(qMax(0, blockId));
|
||||
if (block.isValid())
|
||||
TextDocumentLayout::doFoldOrUnfold(block, false);
|
||||
}
|
||||
|
||||
editor->document()->setModified(true);
|
||||
}
|
||||
|
||||
static void showError(const QString &error)
|
||||
{
|
||||
Core::MessageManager::write(
|
||||
QString(QT_TRANSLATE_NOOP("TextEditor", "Error in text formatting: %1"))
|
||||
.arg(error.trimmed()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the state of @a task and if the formatting was successful calls updateEditorText() with
|
||||
* the respective members of @a task.
|
||||
*/
|
||||
void checkAndApplyTask(const FormatTask &task)
|
||||
{
|
||||
if (!task.error.isEmpty()) {
|
||||
showError(task.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (task.formattedData.isEmpty()) {
|
||||
showError(QString(QT_TRANSLATE_NOOP("TextEditor", "Could not format file %1.")).arg(
|
||||
task.filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
QPlainTextEdit *textEditor = task.editor;
|
||||
if (!textEditor) {
|
||||
showError(QString(QT_TRANSLATE_NOOP("TextEditor", "File %1 was closed.")).arg(
|
||||
task.filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString formattedData = (task.startPos < 0)
|
||||
? task.formattedData
|
||||
: QString(textEditor->toPlainText()).replace(
|
||||
task.startPos, (task.endPos - task.startPos), task.formattedData);
|
||||
|
||||
updateEditorText(textEditor, formattedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the text of @a editor using @a command. @a startPos and @a endPos specifies the range of
|
||||
* the editor's text that will be formatted. If @a startPos is negative the editor's entire text is
|
||||
* formatted.
|
||||
*
|
||||
* @pre @a endPos must be greater than or equal to @a startPos
|
||||
*/
|
||||
void formatEditor(TextEditorWidget *editor, const Command &command, int startPos, int endPos)
|
||||
{
|
||||
QTC_ASSERT(startPos <= endPos, return);
|
||||
|
||||
const QString sd = sourceData(editor, startPos, endPos);
|
||||
if (sd.isEmpty())
|
||||
return;
|
||||
checkAndApplyTask(format(FormatTask(editor, editor->textDocument()->filePath().toString(), sd,
|
||||
command, startPos, endPos)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves like formatEditor except that the formatting is done asynchronously.
|
||||
*/
|
||||
void formatEditorAsync(TextEditorWidget *editor, const Command &command, int startPos, int endPos)
|
||||
{
|
||||
QTC_ASSERT(startPos <= endPos, return);
|
||||
|
||||
const QString sd = sourceData(editor, startPos, endPos);
|
||||
if (sd.isEmpty())
|
||||
return;
|
||||
|
||||
auto *watcher = new QFutureWatcher<FormatTask>;
|
||||
const TextDocument *doc = editor->textDocument();
|
||||
QObject::connect(doc, &TextDocument::contentsChanged, watcher, &QFutureWatcher<FormatTask>::cancel);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::finished, [watcher] {
|
||||
if (watcher->isCanceled())
|
||||
showError(QString(QT_TRANSLATE_NOOP("TextEditor", "File was modified.")));
|
||||
else
|
||||
checkAndApplyTask(watcher->result());
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(Utils::runAsync(&format, FormatTask(editor, doc->filePath().toString(), sd,
|
||||
command, startPos, endPos)));
|
||||
}
|
||||
|
||||
} // namespace TextEditor
|
67
src/plugins/texteditor/formattexteditor.h
Normal file
67
src/plugins/texteditor/formattexteditor.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "texteditor_global.h"
|
||||
|
||||
#include "command.h"
|
||||
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPointer>
|
||||
|
||||
namespace TextEditor {
|
||||
|
||||
class TextEditorWidget;
|
||||
|
||||
class TEXTEDITOR_EXPORT FormatTask
|
||||
{
|
||||
public:
|
||||
FormatTask(QPlainTextEdit *_editor, const QString &_filePath, const QString &_sourceData,
|
||||
const Command &_command, int _startPos = -1, int _endPos = 0) :
|
||||
editor(_editor),
|
||||
filePath(_filePath),
|
||||
sourceData(_sourceData),
|
||||
command(_command),
|
||||
startPos(_startPos),
|
||||
endPos(_endPos) {}
|
||||
|
||||
QPointer<QPlainTextEdit> editor;
|
||||
QString filePath;
|
||||
QString sourceData;
|
||||
TextEditor::Command command;
|
||||
int startPos = -1;
|
||||
int endPos = 0;
|
||||
QString formattedData;
|
||||
QString error;
|
||||
};
|
||||
|
||||
TEXTEDITOR_EXPORT void formatCurrentFile(const TextEditor::Command &command, int startPos = -1, int endPos = 0);
|
||||
TEXTEDITOR_EXPORT void formatEditor(TextEditorWidget *editor, const TextEditor::Command &command,
|
||||
int startPos = -1, int endPos = 0);
|
||||
TEXTEDITOR_EXPORT void formatEditorAsync(TextEditorWidget *editor, const TextEditor::Command &command,
|
||||
int startPos = -1, int endPos = 0);
|
||||
|
||||
} // namespace TextEditor
|
@@ -101,7 +101,9 @@ SOURCES += texteditorplugin.cpp \
|
||||
codeassist/keywordscompletionassist.cpp \
|
||||
completionsettingspage.cpp \
|
||||
commentssettings.cpp \
|
||||
marginsettings.cpp
|
||||
marginsettings.cpp \
|
||||
formattexteditor.cpp \
|
||||
command.cpp
|
||||
|
||||
HEADERS += texteditorplugin.h \
|
||||
plaintexteditorfactory.h \
|
||||
@@ -214,7 +216,9 @@ HEADERS += texteditorplugin.h \
|
||||
blockrange.h \
|
||||
completionsettingspage.h \
|
||||
commentssettings.h \
|
||||
textstyles.h
|
||||
textstyles.h \
|
||||
formattexteditor.h \
|
||||
command.h
|
||||
|
||||
FORMS += \
|
||||
displaysettingspage.ui \
|
||||
|
@@ -51,6 +51,8 @@ Project {
|
||||
"colorschemeedit.cpp",
|
||||
"colorschemeedit.h",
|
||||
"colorschemeedit.ui",
|
||||
"command.cpp",
|
||||
"command.h",
|
||||
"commentssettings.cpp",
|
||||
"commentssettings.h",
|
||||
"completionsettings.cpp",
|
||||
@@ -76,6 +78,8 @@ Project {
|
||||
"fontsettingspage.cpp",
|
||||
"fontsettingspage.h",
|
||||
"fontsettingspage.ui",
|
||||
"formattexteditor.cpp",
|
||||
"formattexteditor.h",
|
||||
"helpitem.cpp",
|
||||
"helpitem.h",
|
||||
"highlighterutils.cpp",
|
||||
|
Reference in New Issue
Block a user