diff options
Diffstat (limited to 'src')
26 files changed, 503 insertions, 397 deletions
diff --git a/src/plugins/diffeditor/differ.cpp b/src/libs/utils/differ.cpp index b810eeff62..cff9dd21db 100644 --- a/src/plugins/diffeditor/differ.cpp +++ b/src/libs/utils/differ.cpp @@ -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 diff --git a/src/plugins/diffeditor/differ.h b/src/libs/utils/differ.h index 14012cc10f..11eebfe527 100644 --- a/src/plugins/diffeditor/differ.h +++ b/src/libs/utils/differ.h @@ -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 diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index 41d8745baa..3369e0e161 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -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 \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 2ada52c17e..40f7a21ca4 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -75,6 +75,8 @@ Project { "detailsbutton.h", "detailswidget.cpp", "detailswidget.h", + "differ.cpp", + "differ.h", "dropsupport.cpp", "dropsupport.h", "elfreader.cpp", diff --git a/src/plugins/beautifier/artisticstyle/artisticstyle.cpp b/src/plugins/beautifier/artisticstyle/artisticstyle.cpp index 40e5cb20cb..4a7ee4ec54 100644 --- a/src/plugins/beautifier/artisticstyle/artisticstyle.cpp +++ b/src/plugins/beautifier/artisticstyle/artisticstyle.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)); } } diff --git a/src/plugins/beautifier/artisticstyle/artisticstyle.h b/src/plugins/beautifier/artisticstyle/artisticstyle.h index 2dbcca4bec..5927814489 100644 --- a/src/plugins/beautifier/artisticstyle/artisticstyle.h +++ b/src/plugins/beautifier/artisticstyle/artisticstyle.h @@ -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 diff --git a/src/plugins/beautifier/beautifier.pro b/src/plugins/beautifier/beautifier.pro index 6dcdbf6e69..65021fd8be 100644 --- a/src/plugins/beautifier/beautifier.pro +++ b/src/plugins/beautifier/beautifier.pro @@ -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 \ diff --git a/src/plugins/beautifier/beautifier.qbs b/src/plugins/beautifier/beautifier.qbs index 35c5a17fc8..1a71cb0098 100644 --- a/src/plugins/beautifier/beautifier.qbs +++ b/src/plugins/beautifier/beautifier.qbs @@ -20,8 +20,6 @@ QtcPlugin { "beautifierconstants.h", "beautifierplugin.cpp", "beautifierplugin.h", - "command.cpp", - "command.h", "configurationdialog.cpp", "configurationdialog.h", "configurationdialog.ui", diff --git a/src/plugins/beautifier/beautifierabstracttool.h b/src/plugins/beautifier/beautifierabstracttool.h index 9253a6d910..813f37ac7f 100644 --- a/src/plugins/beautifier/beautifierabstracttool.h +++ b/src/plugins/beautifier/beautifierabstracttool.h @@ -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; }; diff --git a/src/plugins/beautifier/beautifierplugin.cpp b/src/plugins/beautifier/beautifierplugin.cpp index fa973c717a..3883a79044 100644 --- a/src/plugins/beautifier/beautifierplugin.cpp +++ b/src/plugins/beautifier/beautifierplugin.cpp @@ -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,219 +175,15 @@ 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); - } -} - -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; + TextEditor::formatEditor(widget, command); } - - 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) diff --git a/src/plugins/beautifier/beautifierplugin.h b/src/plugins/beautifier/beautifierplugin.h index 4b55eab80c..c35ef2ab9a 100644 --- a/src/plugins/beautifier/beautifierplugin.h +++ b/src/plugins/beautifier/beautifierplugin.h @@ -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(); diff --git a/src/plugins/beautifier/clangformat/clangformat.cpp b/src/plugins/beautifier/clangformat/clangformat.cpp index ce9d69c9d3..ee2e9ad770 100644 --- a/src/plugins/beautifier/clangformat/clangformat.cpp +++ b/src/plugins/beautifier/clangformat/clangformat.cpp @@ -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 diff --git a/src/plugins/beautifier/clangformat/clangformat.h b/src/plugins/beautifier/clangformat/clangformat.h index 2da33b8190..4d69d2e9e5 100644 --- a/src/plugins/beautifier/clangformat/clangformat.h +++ b/src/plugins/beautifier/clangformat/clangformat.h @@ -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 diff --git a/src/plugins/beautifier/uncrustify/uncrustify.cpp b/src/plugins/beautifier/uncrustify/uncrustify.cpp index acba89331b..daeffdf2df 100644 --- a/src/plugins/beautifier/uncrustify/uncrustify.cpp +++ b/src/plugins/beautifier/uncrustify/uncrustify.cpp @@ -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(); } diff --git a/src/plugins/beautifier/uncrustify/uncrustify.h b/src/plugins/beautifier/uncrustify/uncrustify.h index 3bdeffacc0..e35434fc81 100644 --- a/src/plugins/beautifier/uncrustify/uncrustify.h +++ b/src/plugins/beautifier/uncrustify/uncrustify.h @@ -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 diff --git a/src/plugins/diffeditor/diffeditor.pro b/src/plugins/diffeditor/diffeditor.pro index 5c8c904495..235f4e9dcd 100644 --- a/src/plugins/diffeditor/diffeditor.pro +++ b/src/plugins/diffeditor/diffeditor.pro @@ -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 \ diff --git a/src/plugins/diffeditor/diffeditor.qbs b/src/plugins/diffeditor/diffeditor.qbs index 8d0a1a1820..9c6a41c377 100644 --- a/src/plugins/diffeditor/diffeditor.qbs +++ b/src/plugins/diffeditor/diffeditor.qbs @@ -32,8 +32,6 @@ QtcPlugin { "diffeditorplugin.h", "diffeditorwidgetcontroller.cpp", "diffeditorwidgetcontroller.h", - "differ.cpp", - "differ.h", "diffutils.cpp", "diffutils.h", "diffview.cpp", diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp index 4411c84c67..6d3ba8858d 100644 --- a/src/plugins/diffeditor/diffeditorplugin.cpp +++ b/src/plugins/diffeditor/diffeditorplugin.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 { diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp index e2a1831251..e233173629 100644 --- a/src/plugins/diffeditor/diffutils.cpp +++ b/src/plugins/diffeditor/diffutils.cpp @@ -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; diff --git a/src/plugins/diffeditor/diffutils.h b/src/plugins/diffeditor/diffutils.h index d057a38e2f..31956e6296 100644 --- a/src/plugins/diffeditor/diffutils.h +++ b/src/plugins/diffeditor/diffutils.h @@ -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); diff --git a/src/plugins/beautifier/command.cpp b/src/plugins/texteditor/command.cpp index 6263ae9b52..b709334590 100644 --- a/src/plugins/beautifier/command.cpp +++ b/src/plugins/texteditor/command.cpp @@ -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 diff --git a/src/plugins/beautifier/command.h b/src/plugins/texteditor/command.h index f81880fb3d..dcb2f6f23a 100644 --- a/src/plugins/beautifier/command.h +++ b/src/plugins/texteditor/command.h @@ -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 diff --git a/src/plugins/texteditor/formattexteditor.cpp b/src/plugins/texteditor/formattexteditor.cpp new file mode 100644 index 0000000000..d38c863cca --- /dev/null +++ b/src/plugins/texteditor/formattexteditor.cpp @@ -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 diff --git a/src/plugins/texteditor/formattexteditor.h b/src/plugins/texteditor/formattexteditor.h new file mode 100644 index 0000000000..9e8ab26611 --- /dev/null +++ b/src/plugins/texteditor/formattexteditor.h @@ -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 diff --git a/src/plugins/texteditor/texteditor.pro b/src/plugins/texteditor/texteditor.pro index 508acb78a7..9b698b7340 100644 --- a/src/plugins/texteditor/texteditor.pro +++ b/src/plugins/texteditor/texteditor.pro @@ -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 \ diff --git a/src/plugins/texteditor/texteditor.qbs b/src/plugins/texteditor/texteditor.qbs index 81bb85c0a3..052a8f13fb 100644 --- a/src/plugins/texteditor/texteditor.qbs +++ b/src/plugins/texteditor/texteditor.qbs @@ -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", |