diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index b64217af98..bf78d3dec1 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -1,190 +1,191 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") add_subdirectory(tests) set(KDevPlatformShell_LIB_SRCS workingsetcontroller.cpp workingsets/workingset.cpp workingsets/workingsetfilelabel.cpp workingsets/workingsettoolbutton.cpp workingsets/workingsettooltipwidget.cpp workingsets/workingsetwidget.cpp workingsets/closedworkingsetswidget.cpp workingsets/workingsethelpers.cpp mainwindow.cpp mainwindow_p.cpp plugincontroller.cpp ktexteditorpluginintegration.cpp shellextension.cpp core.cpp uicontroller.cpp colorschemechooser.cpp projectcontroller.cpp project.cpp partcontroller.cpp #document.cpp partdocument.cpp textdocument.cpp documentcontroller.cpp languagecontroller.cpp statusbar.cpp runcontroller.cpp unitylauncher.cpp sessioncontroller.cpp session.cpp sessionlock.cpp sessionchooserdialog.cpp savedialog.cpp sourceformattercontroller.cpp + sourceformatterjob.cpp completionsettings.cpp openprojectpage.cpp openprojectdialog.cpp projectinfopage.cpp selectioncontroller.cpp documentationcontroller.cpp debugcontroller.cpp launchconfiguration.cpp launchconfigurationdialog.cpp loadedpluginsdialog.cpp testcontroller.cpp projectsourcepage.cpp configdialog.cpp editorconfigpage.cpp environmentconfigurebutton.cpp runtimecontroller.cpp checkerstatus.cpp problem.cpp problemmodelset.cpp problemmodel.cpp problemstore.cpp watcheddocumentset.cpp filteredproblemstore.cpp progresswidget/progressmanager.cpp progresswidget/statusbarprogresswidget.cpp progresswidget/overlaywidget.cpp progresswidget/progressdialog.cpp areadisplay.cpp settings/uipreferences.cpp settings/pluginpreferences.cpp settings/sourceformattersettings.cpp settings/editstyledialog.cpp settings/projectpreferences.cpp settings/environmentwidget.cpp settings/environmentprofilemodel.cpp settings/environmentprofilelistmodel.cpp settings/environmentpreferences.cpp settings/languagepreferences.cpp settings/bgpreferences.cpp settings/templateconfig.cpp settings/templatepage.cpp settings/analyzerspreferences.cpp settings/runtimespreferences.cpp settings/documentationpreferences.cpp ) if(APPLE) set(KDevPlatformShell_LIB_SRCS ${KDevPlatformShell_LIB_SRCS} macdockprogressview.mm ) endif() ecm_qt_declare_logging_category(KDevPlatformShell_LIB_SRCS HEADER debug.h IDENTIFIER SHELL CATEGORY_NAME "kdevplatform.shell" ) kconfig_add_kcfg_files(KDevPlatformShell_LIB_SRCS settings/uiconfig.kcfgc settings/projectconfig.kcfgc settings/languageconfig.kcfgc settings/bgconfig.kcfgc ) ki18n_wrap_ui(KDevPlatformShell_LIB_SRCS projectinfopage.ui launchconfigurationdialog.ui projectsourcepage.ui settings/uiconfig.ui settings/editstyledialog.ui settings/sourceformattersettings.ui settings/projectpreferences.ui settings/environmentwidget.ui settings/languagepreferences.ui settings/bgpreferences.ui settings/templateconfig.ui settings/templatepage.ui ) qt5_add_resources(KDevPlatformShell_LIB_SRCS kdevplatformshell.qrc) kdevplatform_add_library(KDevPlatformShell SOURCES ${KDevPlatformShell_LIB_SRCS}) target_link_libraries(KDevPlatformShell LINK_PUBLIC KF5::XmlGui KDev::Sublime KDev::OutputView KDev::Interfaces LINK_PRIVATE KF5::GuiAddons KF5::ConfigWidgets KF5::IconThemes KF5::KIOFileWidgets KF5::KIOWidgets KF5::Parts KF5::Notifications KF5::NotifyConfig KF5::TextEditor KF5::ThreadWeaver KF5::JobWidgets KF5::ItemViews KF5::WindowSystem KF5::KCMUtils #for KPluginSelector, not sure why it is in kcmutils KF5::NewStuff # template config page KF5::Archive # template config page KDev::Debugger KDev::Project KDev::Vcs KDev::Language KDev::Util KDev::Documentation ) if(APPLE) target_link_libraries(KDevPlatformShell PRIVATE "-framework AppKit") endif() install(FILES mainwindow.h plugincontroller.h shellextension.h core.h uicontroller.h colorschemechooser.h projectcontroller.h project.h partcontroller.h partdocument.h textdocument.h documentcontroller.h languagecontroller.h session.h sessioncontroller.h sessionlock.h sourceformattercontroller.h selectioncontroller.h runcontroller.h launchconfiguration.h environmentconfigurebutton.h checkerstatus.h problem.h problemmodel.h problemmodelset.h problemconstants.h filteredproblemstore.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/shell COMPONENT Devel ) diff --git a/shell/sourceformattercontroller.cpp b/shell/sourceformattercontroller.cpp index 629caabc4c..012c69db1f 100644 --- a/shell/sourceformattercontroller.cpp +++ b/shell/sourceformattercontroller.cpp @@ -1,659 +1,629 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sourceformattercontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include "core.h" #include "debug.h" #include "plugincontroller.h" +#include "sourceformatterjob.h" namespace { namespace Strings { QString SourceFormatter() { return QStringLiteral("SourceFormatter"); } } } namespace KDevelop { QString SourceFormatterController::kateModeLineConfigKey() { return QStringLiteral("ModelinesEnabled"); } QString SourceFormatterController::kateOverrideIndentationConfigKey() { return QStringLiteral("OverrideKateIndentation"); } QString SourceFormatterController::styleCaptionKey() { return QStringLiteral("Caption"); } QString SourceFormatterController::styleContentKey() { return QStringLiteral("Content"); } QString SourceFormatterController::styleMimeTypesKey() { return QStringLiteral("MimeTypes"); } QString SourceFormatterController::styleSampleKey() { return QStringLiteral("StyleSample"); } SourceFormatterController::SourceFormatterController(QObject *parent) : ISourceFormatterController(parent), m_enabled(true) { setObjectName(QStringLiteral("SourceFormatterController")); setComponentName(QStringLiteral("kdevsourceformatter"), i18n("Source Formatter")); setXMLFile(QStringLiteral("kdevsourceformatter.rc")); if (Core::self()->setupFlags() & Core::NoUi) return; m_formatTextAction = actionCollection()->addAction(QStringLiteral("edit_reformat_source")); m_formatTextAction->setText(i18n("&Reformat Source")); m_formatTextAction->setToolTip(i18n("Reformat source using AStyle")); m_formatTextAction->setWhatsThis(i18n("Source reformatting functionality using astyle library.")); connect(m_formatTextAction, &QAction::triggered, this, &SourceFormatterController::beautifySource); m_formatLine = actionCollection()->addAction(QStringLiteral("edit_reformat_line")); m_formatLine->setText(i18n("Reformat Line")); m_formatLine->setToolTip(i18n("Reformat current line using AStyle")); m_formatLine->setWhatsThis(i18n("Source reformatting of line under cursor using astyle library.")); connect(m_formatLine, &QAction::triggered, this, &SourceFormatterController::beautifyLine); m_formatFilesAction = actionCollection()->addAction(QStringLiteral("tools_astyle")); m_formatFilesAction->setText(i18n("Reformat Files...")); m_formatFilesAction->setToolTip(i18n("Format file(s) using the current theme")); m_formatFilesAction->setWhatsThis(i18n("Formatting functionality using astyle library.")); connect(m_formatFilesAction, &QAction::triggered, this, static_cast(&SourceFormatterController::formatFiles)); // connect to both documentActivated & documentClosed, // otherwise we miss when the last document was closed connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &SourceFormatterController::updateFormatTextAction); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &SourceFormatterController::updateFormatTextAction); // Use a queued connection, because otherwise the view is not yet fully set up connect(Core::self()->documentController(), &IDocumentController::documentLoaded, this, &SourceFormatterController::documentLoaded, Qt::QueuedConnection); updateFormatTextAction(); } void SourceFormatterController::documentLoaded( IDocument* doc ) { // NOTE: explicitly check this here to prevent crashes on shutdown // when this slot gets called (note: delayed connection) // but the text document was already destroyed // there have been unit tests that failed due to that... if (!doc->textDocument()) { return; } QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); adaptEditorIndentationMode( doc->textDocument(), formatterForMimeType(mime) ); } void SourceFormatterController::initialize() { } SourceFormatterController::~SourceFormatterController() { } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl &url) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); return formatterForMimeType(mime); } KConfigGroup SourceFormatterController::sessionConfig() const { return KDevelop::Core::self()->activeSession()->config()->group( Strings::SourceFormatter() ); } KConfigGroup SourceFormatterController::globalConfig() const { return KSharedConfig::openConfig()->group( Strings::SourceFormatter() ); } ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType(const QMimeType& mime ) const { static QHash knownFormatters; if (knownFormatters.contains(mime.name())) return knownFormatters[mime.name()]; QList plugins = Core::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); foreach( IPlugin* p, plugins) { ISourceFormatter *iformatter = p->extension(); QSharedPointer formatter(createFormatterForPlugin(iformatter)); if( formatter->supportedMimeTypes().contains(mime.name()) ) { knownFormatters[mime.name()] = iformatter; return iformatter; } } knownFormatters[mime.name()] = nullptr; return nullptr; } static void populateStyleFromConfigGroup(SourceFormatterStyle* s, const KConfigGroup& stylegrp) { s->setCaption( stylegrp.readEntry( SourceFormatterController::styleCaptionKey(), QString() ) ); s->setContent( stylegrp.readEntry( SourceFormatterController::styleContentKey(), QString() ) ); s->setMimeTypes( stylegrp.readEntry( SourceFormatterController::styleMimeTypesKey(), QStringList() ) ); s->setOverrideSample( stylegrp.readEntry( SourceFormatterController::styleSampleKey(), QString() ) ); } SourceFormatter* SourceFormatterController::createFormatterForPlugin(ISourceFormatter *ifmt) const { SourceFormatter* formatter = new SourceFormatter(); formatter->formatter = ifmt; // Inserted a new formatter. Now fill it with styles foreach( const KDevelop::SourceFormatterStyle& style, ifmt->predefinedStyles() ) { formatter->styles[ style.name() ] = new SourceFormatterStyle(style); } KConfigGroup grp = globalConfig(); if( grp.hasGroup( ifmt->name() ) ) { KConfigGroup fmtgrp = grp.group( ifmt->name() ); foreach( const QString& subgroup, fmtgrp.groupList() ) { SourceFormatterStyle* s = new SourceFormatterStyle( subgroup ); KConfigGroup stylegrp = fmtgrp.group( subgroup ); populateStyleFromConfigGroup(s, stylegrp); formatter->styles[ s->name() ] = s; } } return formatter; } ISourceFormatter* SourceFormatterController::formatterForMimeType(const QMimeType& mime) { if( !m_enabled || !isMimeTypeSupported( mime ) ) { return nullptr; } QString formatter = sessionConfig().readEntry( mime.name(), QString() ); if( formatter.isEmpty() ) { return findFirstFormatterForMimeType( mime ); } QStringList formatterinfo = formatter.split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatterinfo.size() != 2 ) { qCDebug(SHELL) << "Broken formatting entry for mime:" << mime.name() << "current value:" << formatter; return nullptr; } return Core::self()->pluginControllerInternal()->extensionForPlugin( QStringLiteral("org.kdevelop.ISourceFormatter"), formatterinfo.at(0) ); } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) { if( findFirstFormatterForMimeType( mime ) ) { return true; } return false; } QString SourceFormatterController::indentationMode(const QMimeType& mime) { if (mime.inherits(QStringLiteral("text/x-c++src")) || mime.inherits(QStringLiteral("text/x-chdr")) || mime.inherits(QStringLiteral("text/x-c++hdr")) || mime.inherits(QStringLiteral("text/x-csrc")) || mime.inherits(QStringLiteral("text/x-java")) || mime.inherits(QStringLiteral("text/x-csharp"))) { return QStringLiteral("cstyle"); } return QStringLiteral("none"); } QString SourceFormatterController::addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType& mime) { if( !isMimeTypeSupported(mime) ) return input; QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // If there already is a modeline in the document, adapt it while formatting, even // if "add modeline" is disabled. if( !sessionConfig().readEntry( SourceFormatterController::kateModeLineConfigKey(), false ) && kateModelineWithNewline.indexIn( input ) == -1 ) return input; ISourceFormatter* fmt = formatterForMimeType( mime ); ISourceFormatter::Indentation indentation = fmt->indentation(url); if( !indentation.isValid() ) return input; QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); Q_ASSERT(fmt); QString modeline(QStringLiteral("// kate: ") + QStringLiteral("indent-mode ") + indentationMode(mime) + QStringLiteral("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QStringLiteral("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { modeline.append(QStringLiteral("replace-tabs %1; ").arg(QLatin1String((indentation.indentationTabWidth == -1) ? "on" : "off"))); if(indentation.indentationTabWidth > 0) modeline.append(QStringLiteral("tab-width %1; ").arg(indentation.indentationTabWidth)); } qCDebug(SHELL) << "created modeline: " << modeline << endl; QRegExp kateModeline("^\\s*//\\s*kate:(.*)$"); bool modelinefound = false; QRegExp knownOptions("\\s*(indent-width|space-indent|tab-width|indent-mode|replace-tabs)"); while (!is.atEnd()) { QString line = is.readLine(); // replace only the options we care about if (kateModeline.indexIn(line) >= 0) { // match qCDebug(SHELL) << "Found a kate modeline: " << line << endl; modelinefound = true; QString options = kateModeline.cap(1); QStringList optionList = options.split(';', QString::SkipEmptyParts); os << modeline; foreach(QString s, optionList) { if (knownOptions.indexIn(s) < 0) { // unknown option, add it if(s.startsWith(' ')) s=s.mid(1); os << s << ";"; qCDebug(SHELL) << "Found unknown option: " << s << endl; } } os << endl; } else os << line << endl; } if (!modelinefound) os << modeline << endl; return output; } void SourceFormatterController::cleanup() { } void SourceFormatterController::updateFormatTextAction() { bool enabled = false; IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument(); if (doc) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); if (isMimeTypeSupported(mime)) enabled = true; } m_formatLine->setEnabled(enabled); m_formatTextAction->setEnabled(enabled); } void SourceFormatterController::beautifySource() { IDocument* idoc = KDevelop::ICore::self()->documentController()->activeDocument(); if (!idoc) return; KTextEditor::View* view = idoc->activeTextView(); if (!view) return; KTextEditor::Document* doc = view->document(); // load the appropriate formatter QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } // Ignore the modeline, as the modeline will be changed anyway adaptEditorIndentationMode( doc, formatter, true ); bool has_selection = view->selection(); if (has_selection) { QString original = view->selectionText(); QString output = formatter->formatSource(view->selectionText(), doc->url(), mime, doc->text(KTextEditor::Range(KTextEditor::Cursor(0,0),view->selectionRange().start())), doc->text(KTextEditor::Range(view->selectionRange().end(), doc->documentRange().end()))); //remove the final newline character, unless it should be there if (!original.endsWith('\n') && output.endsWith('\n')) output.resize(output.length() - 1); //there was a selection, so only change the part of the text related to it // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code( dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( view->selectionRange(), original, output ); } else { formatDocument(idoc, formatter, mime); } } void SourceFormatterController::beautifyLine() { KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->activeDocument(); if (!doc || !doc->isTextDocument()) return; KTextEditor::Document *tDoc = doc->textDocument(); KTextEditor::View* view = doc->activeTextView(); if (!view) return; // load the appropriate formatter QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } const KTextEditor::Cursor cursor = view->cursorPosition(); const QString line = tDoc->line(cursor.line()); const QString prev = tDoc->text(KTextEditor::Range(0, 0, cursor.line(), 0)); const QString post = '\n' + tDoc->text(KTextEditor::Range(KTextEditor::Cursor(cursor.line() + 1, 0), tDoc->documentEnd())); const QString formatted = formatter->formatSource(line, doc->url(), mime, prev, post); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code(dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), line, formatted ); // advance cursor one line view->setCursorPosition(KTextEditor::Cursor(cursor.line() + 1, 0)); } void SourceFormatterController::formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime) { Q_ASSERT(doc); Q_ASSERT(formatter); qCDebug(SHELL) << "Running" << formatter->name() << "on" << doc->url(); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop CodeRepresentation::Ptr code = KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ); KTextEditor::Cursor cursor = doc->cursorPosition(); QString text = formatter->formatSource(code->text(), doc->url(), mime); text = addModelineForCurrentLang(text, doc->url(), mime); code->setText(text); doc->setCursorPosition(cursor); } void SourceFormatterController::settingsChanged() { if( sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) ) foreach( KDevelop::IDocument* doc, ICore::self()->documentController()->openDocuments() ) adaptEditorIndentationMode( doc->textDocument(), formatterForUrl(doc->url()) ); } /** * Kate commands: * Use spaces for indentation: * "set-replace-tabs 1" * Use tabs for indentation (eventually mixed): * "set-replace-tabs 0" * Indent width: * "set-indent-width X" * Tab width: * "set-tab-width X" * */ void SourceFormatterController::adaptEditorIndentationMode(KTextEditor::Document *doc, ISourceFormatter *formatter, bool ignoreModeline ) { if( !formatter || !sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) || !doc ) return; qCDebug(SHELL) << "adapting mode for" << doc->url(); QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // modelines should always take precedence if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->text() ) != -1 ) { qCDebug(SHELL) << "ignoring because a kate modeline was found"; return; } ISourceFormatter::Indentation indentation = formatter->indentation(doc->url()); if(indentation.isValid()) { struct CommandCaller { explicit CommandCaller(KTextEditor::Document* _doc) : doc(_doc), editor(KTextEditor::Editor::instance()) { Q_ASSERT(editor); } void operator()(QString cmd) { KTextEditor::Command* command = editor->queryCommand( cmd ); Q_ASSERT(command); QString msg; qCDebug(SHELL) << "calling" << cmd; foreach(KTextEditor::View* view, doc->views()) if( !command->exec( view, cmd, msg ) ) qCWarning(SHELL) << "setting indentation width failed: " << msg; } KTextEditor::Document* doc; KTextEditor::Editor* editor; } call(doc); if( indentation.indentWidth ) // We know something about indentation-width call( QStringLiteral("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QStringLiteral("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QStringLiteral("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }else{ qCDebug(SHELL) << "found no valid indentation"; } } void SourceFormatterController::formatFiles() { if (m_prjItems.isEmpty()) return; //get a list of all files in this folder recursively QList folders; foreach(KDevelop::ProjectBaseItem *item, m_prjItems) { if (!item) continue; if (item->folder()) folders.append(item->folder()); else if (item->file()) m_urls.append(item->file()->path().toUrl()); else if (item->target()) { foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } } while (!folders.isEmpty()) { KDevelop::ProjectFolderItem *item = folders.takeFirst(); foreach(KDevelop::ProjectFolderItem *f, item->folderList()) folders.append(f); foreach(KDevelop::ProjectTargetItem *f, item->targetList()) { foreach(KDevelop::ProjectFileItem *child, f->fileList()) m_urls.append(child->path().toUrl()); } foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } auto win = ICore::self()->uiController()->activeMainWindow()->window(); QMessageBox msgBox(QMessageBox::Question, i18n("Reformat files?"), i18n("Reformat all files in the selected folder?"), QMessageBox::Ok|QMessageBox::Cancel, win); msgBox.setDefaultButton(QMessageBox::Cancel); auto okButton = msgBox.button(QMessageBox::Ok); okButton->setText(i18n("Reformat")); msgBox.exec(); if (msgBox.clickedButton() == okButton) { - formatFiles(m_urls); - } -} - -void SourceFormatterController::formatFiles(QList &list) -{ - //! \todo IStatus - for (int fileCount = 0; fileCount < list.size(); fileCount++) { - // check mimetype - QMimeType mime = QMimeDatabase().mimeTypeForUrl(list[fileCount]); - qCDebug(SHELL) << "Checking file " << list[fileCount] << " of mime type " << mime.name() << endl; - ISourceFormatter *formatter = formatterForMimeType(mime); - if (!formatter) // unsupported mime type - continue; - - // if the file is opened in the editor, format the text in the editor without saving it - KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); - KDevelop::IDocument *doc = docController->documentForUrl(list[fileCount]); - if (doc) { - qCDebug(SHELL) << "Processing file " << list[fileCount] << "opened in editor" << endl; - formatDocument(doc, formatter, mime); - continue; - } - - qCDebug(SHELL) << "Processing file " << list[fileCount] << endl; - KIO::StoredTransferJob *job = KIO::storedGet(list[fileCount]); - if (job->exec()) { - QString text = QString::fromLocal8Bit(job->data()); - text = formatter->formatSource(text, list[fileCount], mime); - text = addModelineForCurrentLang(text, list[fileCount], mime).toUtf8(); - job = KIO::storedPut(text.toLocal8Bit(), list[fileCount], -1, KIO::Overwrite); - if (!job->exec()) - KMessageBox::error(nullptr, job->errorString()); - } else - KMessageBox::error(nullptr, job->errorString()); + auto formatterJob = new SourceFormatterJob(this); + formatterJob->setFiles(m_urls); + ICore::self()->runController()->registerJob(formatterJob); } } KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { Q_UNUSED(parent); KDevelop::ContextMenuExtension ext; m_urls.clear(); m_prjItems.clear(); if (context->hasType(KDevelop::Context::EditorContext)) { if(m_formatTextAction->isEnabled()) ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatTextAction); } else if (context->hasType(KDevelop::Context::FileContext)) { KDevelop::FileContext* filectx = static_cast(context); m_urls = filectx->urls(); ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatFilesAction); } else if (context->hasType(KDevelop::Context::CodeContext)) { } else if (context->hasType(KDevelop::Context::ProjectItemContext)) { KDevelop::ProjectItemContext* prjctx = static_cast(context); m_prjItems = prjctx->items(); if ( !m_prjItems.isEmpty() ) { ext.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_formatFilesAction); } } return ext; } SourceFormatterStyle SourceFormatterController::styleForMimeType(const QMimeType& mime) { QStringList formatter = sessionConfig().readEntry( mime.name(), QString() ).split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatter.count() == 2 ) { SourceFormatterStyle s( formatter.at( 1 ) ); KConfigGroup fmtgrp = globalConfig().group( formatter.at(0) ); if( fmtgrp.hasGroup( formatter.at(1) ) ) { KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) ); populateStyleFromConfigGroup(&s, stylegrp); } return s; } return SourceFormatterStyle(); } void SourceFormatterController::disableSourceFormatting(bool disable) { m_enabled = !disable; } bool SourceFormatterController::sourceFormattingEnabled() { return m_enabled; } } diff --git a/shell/sourceformattercontroller.h b/shell/sourceformattercontroller.h index c353735ae8..7c036e0263 100644 --- a/shell/sourceformattercontroller.h +++ b/shell/sourceformattercontroller.h @@ -1,164 +1,166 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #define KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #include #include #include #include #include #include #include #include #include "shellexport.h" namespace KTextEditor { class Document; } class QAction; namespace KDevelop { class Context; class ContextMenuExtension; class ProjectBaseItem; class IDocument; class ISourceFormatter; class IPlugin; struct SourceFormatter { KDevelop::ISourceFormatter* formatter; // style name -> style. style objects owned by this typedef QMap StyleMap; StyleMap styles; // Get a list of supported mime types from the style map. QSet supportedMimeTypes() const { QSet supported; for ( auto style: styles ) { foreach ( auto& item, style->mimeTypes() ) { supported.insert(item.mimeType); } } return supported; } ~SourceFormatter() { qDeleteAll(styles); }; }; /** \short A singleton class managing all source formatter plugins */ class KDEVPLATFORMSHELL_EXPORT SourceFormatterController : public ISourceFormatterController, public KXMLGUIClient { Q_OBJECT + + friend class SourceFormatterJob; + public: static QString kateModeLineConfigKey(); static QString kateOverrideIndentationConfigKey(); static QString styleCaptionKey(); static QString styleContentKey(); static QString styleMimeTypesKey(); static QString styleSampleKey(); explicit SourceFormatterController(QObject *parent = nullptr); ~SourceFormatterController() override; void initialize(); void cleanup(); //----------------- Public API defined in interfaces ------------------- /** \return The formatter corresponding to the language * of the document corresponding to the \arg url. */ ISourceFormatter* formatterForUrl(const QUrl &url) override; /** Loads and returns a source formatter for this mime type. * The language is then activated and the style is loaded. * The source formatter is then ready to use on a file. */ ISourceFormatter* formatterForMimeType(const QMimeType& mime) override; /** \return Whether this mime type is supported by any plugin. */ bool isMimeTypeSupported(const QMimeType& mime) override; /** * @brief Instantiate a Formatter for the given plugin and load its configuration. * * @param ifmt The ISourceFormatter interface of the plugin * @return KDevelop::SourceFormatter* the SourceFormatter instance for the plugin, including config items */ SourceFormatter* createFormatterForPlugin(KDevelop::ISourceFormatter* ifmt) const; /** * @brief Find the first formatter which supports a given mime type. */ ISourceFormatter* findFirstFormatterForMimeType(const QMimeType& mime) const; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent); KDevelop::SourceFormatterStyle styleForMimeType(const QMimeType& mime) override; KConfigGroup sessionConfig() const; KConfigGroup globalConfig() const; void settingsChanged(); void disableSourceFormatting(bool disable) override; bool sourceFormattingEnabled() override; private Q_SLOTS: void updateFormatTextAction(); void beautifySource(); void beautifyLine(); void formatFiles(); void documentLoaded( KDevelop::IDocument* ); private: /** \return A modeline string (to add at the end or the beginning of a file) * corresponding to the settings of the active language. */ QString addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType&); /** \return The name of kate indentation mode for the mime type. * examples are cstyle, python, etc. */ QString indentationMode(const QMimeType& mime); void formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime); // Adapts the mode of the editor regarding indentation-style void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter, bool ignoreModeline = false); - void formatFiles(QList &list); // GUI actions QAction* m_formatTextAction; QAction* m_formatFilesAction; QAction* m_formatLine; QList m_prjItems; QList m_urls; bool m_enabled; }; } #endif // KDEVPLATFORM_SOURCEFORMATTERMANAGER_H diff --git a/shell/sourceformatterjob.cpp b/shell/sourceformatterjob.cpp new file mode 100644 index 0000000000..9ed198f233 --- /dev/null +++ b/shell/sourceformatterjob.cpp @@ -0,0 +1,153 @@ +/* + * This file is part of KDevelop + * + * Copyright (C) 2008 Cédric Pasteur + * Copyright 2009 Andreas Pakulat + * Copyright 2017 Friedrich W. H. Kossebau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "sourceformatterjob.h" + +#include "sourceformattercontroller.h" + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace KDevelop; + + +SourceFormatterJob::SourceFormatterJob(SourceFormatterController* sourceFormatterController) + : KJob(sourceFormatterController) + , m_sourceFormatterController(sourceFormatterController) + , m_workState(WorkIdle) + , m_fileIndex(0) +{ + setCapabilities(Killable); + // set name for job listing + setObjectName(i18n("Reformatting")); + + KDevelop::ICore::self()->uiController()->registerStatus(this); + + connect(this, &SourceFormatterJob::finished, this, [this]() { + emit hideProgress(this); + }); +} + +QString SourceFormatterJob::statusName() const +{ + return i18n("Reformat Files"); +} + +void SourceFormatterJob::doWork() +{ + // TODO: consider to use ExecuteCompositeJob, with every file a separate subjob + switch (m_workState) { + case WorkIdle: + m_workState = WorkFormat; + m_fileIndex = 0; + emit showProgress(this, 0, 0, 0); + emit showMessage(this, i18np("Reformatting one file", + "Reformatting %1 files", + m_fileList.length())); + + QMetaObject::invokeMethod(this, "doWork", Qt::QueuedConnection); + break; + case WorkFormat: + if (m_fileIndex < m_fileList.length()) { + emit showProgress(this, 0, m_fileList.length(), m_fileIndex); + formatFile(m_fileList[m_fileIndex]); + + // trigger formatting of next file + ++m_fileIndex; + QMetaObject::invokeMethod(this, "doWork", Qt::QueuedConnection); + } else { + m_workState = WorkIdle; + emitResult(); + } + break; + case WorkCancelled: + break; + } +} + +void SourceFormatterJob::start() +{ + if (m_workState != WorkIdle) + return; + + m_workState = WorkIdle; + + QMetaObject::invokeMethod(this, "doWork", Qt::QueuedConnection); +} + +bool SourceFormatterJob::doKill() +{ + m_workState = WorkCancelled; + return true; +} + +void SourceFormatterJob::setFiles(const QList& fileList) +{ + m_fileList = fileList; +} + +void SourceFormatterJob::formatFile(const QUrl& url) +{ + // check mimetype + QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); + qCDebug(SHELL) << "Checking file " << url << " of mime type " << mime.name() << endl; + auto formatter = m_sourceFormatterController->formatterForMimeType(mime); + if (!formatter) // unsupported mime type + return; + + // if the file is opened in the editor, format the text in the editor without saving it + auto doc = ICore::self()->documentController()->documentForUrl(url); + if (doc) { + qCDebug(SHELL) << "Processing file " << url << "opened in editor" << endl; + m_sourceFormatterController->formatDocument(doc, formatter, mime); + return; + } + + qCDebug(SHELL) << "Processing file " << url << endl; + auto getJob = KIO::storedGet(url); + // TODO: make also async and use start() and integrate using setError and setErrorString. + if (getJob->exec()) { + // TODO: really fromLocal8Bit/toLocal8Bit? no encoding detection? added in b8062f736a2bf2eec098af531a7fda6ebcdc7cde + QString text = QString::fromLocal8Bit(getJob->data()); + text = formatter->formatSource(text, url, mime); + text = m_sourceFormatterController->addModelineForCurrentLang(text, url, mime).toUtf8(); + + auto putJob = KIO::storedPut(text.toLocal8Bit(), url, -1, KIO::Overwrite); + // see getJob + if (!putJob->exec()) + // TODO: integrate with job error reporting, use showErrorMessage? + KMessageBox::error(nullptr, putJob->errorString()); + } else + KMessageBox::error(nullptr, getJob->errorString()); +} diff --git a/shell/sourceformatterjob.h b/shell/sourceformatterjob.h new file mode 100644 index 0000000000..3482b7228e --- /dev/null +++ b/shell/sourceformatterjob.h @@ -0,0 +1,85 @@ +/* + * This file is part of KDevelop + * + * Copyright 2017 Friedrich W. H. Kossebau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KDEVPLATFORM_SOURCEFORMATTERJOB_H +#define KDEVPLATFORM_SOURCEFORMATTERJOB_H + +#include +#include + +#include + +#include + + +namespace KDevelop +{ +class SourceFormatterController; + + +class SourceFormatterJob : public KJob, public IStatus +{ + Q_OBJECT + Q_INTERFACES( KDevelop::IStatus ) + +public: + explicit SourceFormatterJob(SourceFormatterController* sourceFormatterController); + +public: // KJob API + void start() override; + +public: // KDevelop::IStatus API + QString statusName() const override; + +public: + void setFiles(const QList& fileList); + +protected: // KJob API + bool doKill() override; + +Q_SIGNALS: // KDevelop::IStatus API + void clearMessage(KDevelop::IStatus* status) override; + void showMessage(KDevelop::IStatus* status, const QString& message, int timeout = 0) override; + void showErrorMessage(const QString& message, int timeout = 0) override; + void hideProgress(KDevelop::IStatus* status) override; + void showProgress(KDevelop::IStatus* status, int minimum, int maximum, int value) override; + +private: + Q_INVOKABLE void doWork(); + + void formatFile(const QUrl& url); + +private: + SourceFormatterController* m_sourceFormatterController; + + enum { + WorkIdle, + WorkFormat, + WorkCancelled + } m_workState; + + QList m_fileList; + int m_fileIndex; +}; + +} + +#endif