diff --git a/plugins/astyle/astyle_plugin.cpp b/plugins/astyle/astyle_plugin.cpp index d027dfdf8f..1b26bb6fce 100644 --- a/plugins/astyle/astyle_plugin.cpp +++ b/plugins/astyle/astyle_plugin.cpp @@ -1,271 +1,271 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur Copyright (C) 2001 Matthias Hölzer-Klüpfel This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "astyle_plugin.h" #include #include #include #include #include "astyle_formatter.h" #include "astyle_stringiterator.h" #include "astyle_preferences.h" #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(AStyleFactory, "kdevastyle.json", registerPlugin();) AStylePlugin::AStylePlugin(QObject *parent, const QVariantList&) : IPlugin(QStringLiteral("kdevastyle"), parent) , m_formatter(new AStyleFormatter()) { currentStyle = predefinedStyles().at(0); } AStylePlugin::~AStylePlugin() { } QString AStylePlugin::name() const { // This needs to match the X-KDE-PluginInfo-Name entry from the .desktop file! return QStringLiteral("kdevastyle"); } QString AStylePlugin::caption() const { return QStringLiteral("Artistic Style"); } QString AStylePlugin::description() const { return i18n("Artistic Style is a source code indenter, formatter," " and beautifier for the C, C++, C# and Java programming languages.
" "Home Page: http://astyle.sourceforge.net"); } QString AStylePlugin::formatSourceWithStyle( SourceFormatterStyle s, const QString& text, const QUrl& /*url*/, const QMimeType& mime, const QString& leftContext, const QString& rightContext ) const { if(mime.inherits(QStringLiteral("text/x-java"))) m_formatter->setJavaStyle(); else if(mime.inherits(QStringLiteral("text/x-csharp"))) m_formatter->setSharpStyle(); else m_formatter->setCStyle(); if( s.content().isEmpty() ) { m_formatter->predefinedStyle( s.name() ); } else { m_formatter->loadStyle( s.content() ); } return m_formatter->formatSource(text, leftContext, rightContext); } QString AStylePlugin::formatSource(const QString& text, const QUrl &url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const { auto style = ICore::self()->sourceFormatterController()->styleForUrl(url, mime); return formatSourceWithStyle(style, text, url, mime, leftContext, rightContext); } static SourceFormatterStyle::MimeList supportedMimeTypes() { return { {QStringLiteral("text/x-c++src"), QStringLiteral("C++")}, {QStringLiteral("text/x-chdr"), QStringLiteral("C")}, {QStringLiteral("text/x-c++hdr"), QStringLiteral("C++")}, {QStringLiteral("text/x-csrc"), QStringLiteral("C")}, {QStringLiteral("text/x-java"), QStringLiteral("Java")}, {QStringLiteral("text/x-csharp"), QStringLiteral("C#")}, }; } SourceFormatterStyle predefinedStyle(const QString& name, const QString& caption = QString()) { SourceFormatterStyle st = SourceFormatterStyle( name ); st.setCaption( caption.isEmpty() ? name : caption ); AStyleFormatter fmt; fmt.predefinedStyle( name ); st.setContent( fmt.saveStyle() ); st.setMimeTypes(supportedMimeTypes()); st.setUsePreview(true); return st; } QVector AStylePlugin::predefinedStyles() const { return { predefinedStyle(QStringLiteral("ANSI")), predefinedStyle(QStringLiteral("GNU")), predefinedStyle(QStringLiteral("Java")), predefinedStyle(QStringLiteral("K&R"), QStringLiteral("Kernighan & Ritchie")), predefinedStyle(QStringLiteral("Linux")), predefinedStyle(QStringLiteral("Stroustrup")), predefinedStyle(QStringLiteral("Horstmann")), predefinedStyle(QStringLiteral("Whitesmith")), predefinedStyle(QStringLiteral("Banner")), predefinedStyle(QStringLiteral("1TBS")), predefinedStyle(QStringLiteral("KDELibs")), predefinedStyle(QStringLiteral("Qt")), }; } SettingsWidget* AStylePlugin::editStyleWidget(const QMimeType& mime) const { AStylePreferences::Language lang = AStylePreferences::CPP; if(mime.inherits(QStringLiteral("text/x-java"))) lang = AStylePreferences::Java; else if(mime.inherits(QStringLiteral("text/x-csharp"))) lang = AStylePreferences::CSharp; return new AStylePreferences(lang); } QString AStylePlugin::previewText(const SourceFormatterStyle& /*style*/, const QMimeType& /*mime*/) const { return QLatin1String("// Indentation\n") + indentingSample() + QLatin1String("\t// Formatting\n") + formattingSample(); } AStylePlugin::Indentation AStylePlugin::indentation(const QUrl& url) const { // Call formatSource first, to initialize the m_formatter data structures according to the URL formatSource(QString(), url, QMimeDatabase().mimeTypeForUrl(url), QString(), QString()); Indentation ret; ret.indentWidth = m_formatter->option(QStringLiteral("FillCount")).toInt(); QString s = m_formatter->option(QStringLiteral("Fill")).toString(); if(s == QLatin1String("Tabs")) { // Do tabs-only indentation ret.indentationTabWidth = ret.indentWidth; }else{ // Don't use tabs at all ret.indentationTabWidth = -1; } return ret; } QString AStylePlugin::formattingSample() { - return QLatin1String( + return QStringLiteral( "void func(){\n" "\tif(isFoo(a,b))\n" "\tbar(a,b);\n" "if(isFoo)\n" "\ta=bar((b-c)*a,*d--);\n" "if( isFoo( a,b ) )\n" "\tbar(a, b);\n" "if (isFoo) {isFoo=false;cat << isFoo <::const_iterator it = list.begin();\n" "}\n" "namespace A {\n" "namespace B {\n" "class someClass {\n" "void foo() {\n" " if (true) {\n" " func();\n" " } else {\n" " // bla\n" " }\n" "}\n" "};\n" "}\n" "}\n" ); } QString AStylePlugin::indentingSample() { - return QLatin1String( + return QStringLiteral( "#define foobar(A)\\\n" "{Foo();Bar();}\n" "#define anotherFoo(B)\\\n" "return Bar()\n" "\n" "namespace Bar\n" "{\n" "class Foo\n" "{public:\n" "Foo();\n" "virtual ~Foo();\n" "};\n" "void bar(int foo)\n" "{\n" "switch (foo)\n" "{\n" "case 1:\n" "a+=1;\n" "break;\n" "case 2:\n" "{\n" "a += 2;\n" " break;\n" "}\n" "}\n" "if (isFoo)\n" "{\n" "bar();\n" "}\n" "else\n" "{\n" "anotherBar();\n" "}\n" "}\n" "int foo()\n" "\twhile(isFoo)\n" "\t\t{\n" "\t\t\t// ...\n" "\t\t\tgoto error;\n" "\t\t/* .... */\n" "\t\terror:\n" "\t\t\t//...\n" "\t\t}\n" "\t}\n" "fooArray[]={ red,\n" "\tgreen,\n" "\tdarkblue};\n" "fooFunction(barArg1,\n" "\tbarArg2,\n" "\tbarArg3);\n" "struct foo{ int bar() {} };\n" ); } #include "astyle_plugin.moc" diff --git a/plugins/custom-definesandincludes/includepathsconverter.cpp b/plugins/custom-definesandincludes/includepathsconverter.cpp index ac405b32e5..2be3d71eca 100644 --- a/plugins/custom-definesandincludes/includepathsconverter.cpp +++ b/plugins/custom-definesandincludes/includepathsconverter.cpp @@ -1,246 +1,246 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 "includepathsconverter.h" #include #include #include #include #include #include #include #include "settingsmanager.h" using namespace KDevelop; namespace { KSharedConfigPtr openConfigFile(const QString& configFile) { return KSharedConfig::openConfig(configFile, KConfig::SimpleConfig); } QString findconfigFile(const QString& projectDir) { QDirIterator dirIterator(projectDir + QLatin1String("/.kdev4")); while (dirIterator.hasNext()) { dirIterator.next(); if (dirIterator.fileName().endsWith(QLatin1String(".kdev4"))) { return dirIterator.fileInfo().canonicalFilePath(); } } return {}; } QString findProject(const QString& subdirectory) { QDir project(subdirectory); do { if (project.exists(QStringLiteral(".kdev4"))) { return project.path(); } } while(project.cdUp()); return {}; } } IncludePathsConverter::IncludePathsConverter() { } bool IncludePathsConverter::addIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory) { auto configFile = openConfigFile(projectConfigFile); if (!configFile) { return false; } auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data()); QString path = subdirectory.isEmpty() ? QStringLiteral(".") : subdirectory; ConfigEntry config; for (auto& entry: configEntries) { if (path == entry.path) { config = entry; config.includes += includeDirectories; config.includes.removeDuplicates(); entry = config; break; } } if (config.path.isEmpty()) { config.path = path; config.includes = includeDirectories; configEntries.append(config); } SettingsManager::globalInstance()->writePaths(configFile.data(), configEntries); return true; } bool IncludePathsConverter::removeIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory) { auto configFile = openConfigFile(projectConfigFile); if (!configFile) { return false; } auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data()); QString path = subdirectory.isEmpty() ? QStringLiteral(".") : subdirectory; for (auto& entry: configEntries) { if (path == entry.path) { for(const auto& include: includeDirectories) { entry.includes.removeAll(include); } SettingsManager::globalInstance()->writePaths(configFile.data(), configEntries); return true; } } return true; } QStringList IncludePathsConverter::readIncludePaths(const QString& projectConfigFile, const QString& subdirectory) const { auto configFile = openConfigFile(projectConfigFile); if (!configFile) { return {}; } QString path = subdirectory.isEmpty() ? QStringLiteral(".") : subdirectory; auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data()); for (const auto& entry: configEntries) { if (path == entry.path) { return entry.includes; } } return {}; } int main(int argc, char** argv) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationName(QStringLiteral("kdev_includepathsconverter")); QCommandLineParser parser; - parser.setApplicationDescription(QLatin1String("\nAdds, removes or shows include directories of a project. Also it can be used as a tool to convert include directories from .kdev_include_paths file to the new format.\n\n" + parser.setApplicationDescription(QStringLiteral("\nAdds, removes or shows include directories of a project. Also it can be used as a tool to convert include directories from .kdev_include_paths file to the new format.\n\n" "Examples:\ncat /project/path/.kdev_include_paths | xargs -d '\\n' kdev_includepathsconverter -a /project/path/\n\n" "kdev_includepathsconverter -r /project/path/subdirectory/ \"/some/include/dir\" \"/another/include/dir\" \n\n" "kdev_includepathsconverter -l /project/path/another/subdirectory/")); parser.addHelpOption(); QCommandLineOption listOption(QStringLiteral("l"), QCoreApplication::translate("main", "Shows include directories used by the project"), QCoreApplication::translate("main", "project")); parser.addOption(listOption); QCommandLineOption addOption(QStringLiteral("a"), QCoreApplication::translate("main", "Adds include directories to the project"), QCoreApplication::translate("main", "project")); parser.addOption(addOption); QCommandLineOption removeOption(QStringLiteral("r"), QCoreApplication::translate("main", "Removes include directories from the project"), QCoreApplication::translate("main", "project")); parser.addOption(removeOption); parser.process(app); QString projectDir; QStringList includeDirectories(parser.positionalArguments()); std::transform(includeDirectories.begin(), includeDirectories.end(), includeDirectories.begin(), [](const QString& path) { return path.trimmed(); } ); includeDirectories.erase(std::remove_if(includeDirectories.begin(), includeDirectories.end(), [](const QString& path) { return path.isEmpty(); } ), includeDirectories.end()); bool show = parser.isSet(listOption); bool add = parser.isSet(addOption); bool remove = parser.isSet(removeOption); if (show) { projectDir = parser.value(listOption); } else if(add) { projectDir = parser.value(addOption); } else if(remove) { projectDir = parser.value(removeOption); } if (projectDir.isEmpty()) { parser.showHelp(-1); } QString subdirectory = projectDir; projectDir = findProject(projectDir); QString configFile = findconfigFile(projectDir); QTextStream out(stdout); if (configFile.isEmpty()) { out << QCoreApplication::translate("main", "No project found for: ") << subdirectory; return -1; } if (add || remove) { if (includeDirectories.isEmpty()) { parser.showHelp(-1); } } { auto subdirCanonical = QFileInfo(subdirectory).canonicalFilePath(); auto projectCanonical = QFileInfo(projectDir).canonicalFilePath(); if (subdirCanonical != projectCanonical) { subdirectory = subdirCanonical.mid(projectCanonical.size()); if (subdirectory.startsWith(QLatin1Char('/'))) { subdirectory.remove(0,1); } } else { subdirectory.clear(); } } IncludePathsConverter converter; if (remove) { if (!converter.removeIncludePaths(includeDirectories, configFile, subdirectory)) { out << QCoreApplication::translate("main", "Can't remove include paths"); } } if (add) { if (!converter.addIncludePaths(includeDirectories, configFile, subdirectory)) { out << QCoreApplication::translate("main", "Can't add include paths"); } } if (show) { const auto& includes = converter.readIncludePaths(configFile, subdirectory); for (const auto& include : includes) { out << include << "\n"; } } } diff --git a/plugins/custommake/makefileresolver/makefileresolver.cpp b/plugins/custommake/makefileresolver/makefileresolver.cpp index d946bca69c..1ece88ba2e 100644 --- a/plugins/custommake/makefileresolver/makefileresolver.cpp +++ b/plugins/custommake/makefileresolver/makefileresolver.cpp @@ -1,641 +1,641 @@ /* * KDevelop C++ Language Support * * Copyright 2007 David Nolden * Copyright 2014 Kevin Funk * * 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 "makefileresolver.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include #include // #define VERBOSE #if defined(VERBOSE) #define ifTest(x) x #else #define ifTest(x) #endif const int maximumInternalResolutionDepth = 3; using namespace std; using namespace KDevelop; namespace { ///After how many seconds should we retry? static const int CACHE_FAIL_FOR_SECONDS = 200; static const int processTimeoutSeconds = 30; struct CacheEntry { CacheEntry() { } ModificationRevisionSet modificationTime; Path::List paths; Path::List frameworkDirectories; QHash defines; QString errorMessage, longErrorMessage; bool failed = false; QMap failedFiles; QDateTime failTime; }; typedef QMap Cache; static Cache s_cache; static QMutex s_cacheMutex; } /** * Compatibility: * make/automake: Should work perfectly * cmake: Thanks to the path-recursion, this works with cmake(tested with version "2.4-patch 6" * with kdelibs out-of-source and with kdevelop4 in-source) **/ class SourcePathInformation { public: explicit SourcePathInformation(const QString& path) : m_path(path) { } QString createCommand(const QString& absoluteFile, const QString& workingDirectory, const QString& makeParameters) const { QString relativeFile = Path(workingDirectory).relativePath(Path(absoluteFile)); #ifndef Q_OS_FREEBSD // GNU make implicitly enables "-w" for sub-makes, we don't want that QLatin1String noPrintDirFlag = QLatin1String(" --no-print-directory"); #else QLatin1String noPrintDirFlag; #endif return QLatin1String("make -k") + noPrintDirFlag + QLatin1String(" -W \'") + absoluteFile + QLatin1String("\' -W \'") + relativeFile + QLatin1String("\' -n ") + makeParameters; } bool hasMakefile() const { QFileInfo makeFile(m_path, QStringLiteral("Makefile")); return makeFile.exists(); } QStringList possibleTargets(const QString& targetBaseName) const { const QStringList ret{ ///@todo open the make-file, and read the target-names from there. //It would be nice if all targets could be processed in one call, the problem is the exit-status of make, so for now make has to be called multiple times. targetBaseName + QLatin1String(".o"), targetBaseName + QLatin1String(".lo"), //ret << targetBaseName + ".lo " + targetBaseName + ".o"; targetBaseName + QLatin1String(".ko"), }; return ret; } private: QString m_path; }; static void mergePaths(KDevelop::Path::List& destList, const KDevelop::Path::List& srcList) { for (const Path& path : srcList) { if (!destList.contains(path)) destList.append(path); } } void PathResolutionResult::mergeWith(const PathResolutionResult& rhs) { mergePaths(paths, rhs.paths); mergePaths(frameworkDirectories, rhs.frameworkDirectories); includePathDependency += rhs.includePathDependency; defines.unite(rhs.defines); } PathResolutionResult::PathResolutionResult(bool success, const QString& errorMessage, const QString& longErrorMessage) : success(success) , errorMessage(errorMessage) , longErrorMessage(longErrorMessage) {} PathResolutionResult::operator bool() const { return success; } ModificationRevisionSet MakeFileResolver::findIncludePathDependency(const QString& file) { QString oldSourceDir = m_source; QString oldBuildDir = m_build; Path currentWd(mapToBuild(file)); ModificationRevisionSet rev; while (currentWd.hasParent()) { currentWd = currentWd.parent(); QString path = currentWd.toLocalFile(); QFileInfo makeFile(QDir(path), QStringLiteral("Makefile")); if (makeFile.exists()) { IndexedString makeFileStr(makeFile.filePath()); rev.addModificationRevision(makeFileStr, ModificationRevision::revisionForFile(makeFileStr)); break; } } setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); return rev; } bool MakeFileResolver::executeCommand(const QString& command, const QString& workingDirectory, QString& result) const { ifTest(cout << "executing " << command.toUtf8().constData() << endl); ifTest(cout << "in " << workingDirectory.toUtf8().constData() << endl); KProcess proc; proc.setWorkingDirectory(workingDirectory); proc.setOutputChannelMode(KProcess::MergedChannels); QStringList args(command.split(QLatin1Char(' '))); QString prog = args.takeFirst(); proc.setProgram(prog, args); int status = proc.execute(processTimeoutSeconds * 1000); result = QString::fromUtf8(proc.readAll()); return status == 0; } MakeFileResolver::MakeFileResolver() { } ///More efficient solution: Only do exactly one call for each directory. During that call, mark all source-files as changed, and make all targets for those files. PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file) { if (file.isEmpty()) { // for unit tests with temporary files return PathResolutionResult(); } QFileInfo fi(file); return resolveIncludePath(fi.fileName(), fi.absolutePath()); } QString MakeFileResolver::mapToBuild(const QString &path) const { QString wd = QDir::cleanPath(path); if (m_outOfSource) { if (wd.startsWith(m_source) && !wd.startsWith(m_build)) { //Move the current working-directory out of source, into the build-system wd = QDir::cleanPath(m_build + QLatin1Char('/') + wd.midRef(m_source.length())); } } return wd; } void MakeFileResolver::clearCache() { QMutexLocker l(&s_cacheMutex); s_cache.clear(); } PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file, const QString& _workingDirectory, int maxStepsUp) { //Prefer this result when returning a "fail". The include-paths of this result will always be added. PathResolutionResult resultOnFail; if (m_isResolving) return PathResolutionResult(false, i18n("Tried include path resolution while another resolution process was still running")); //Make the working-directory absolute QString workingDirectory = _workingDirectory; if (QFileInfo(workingDirectory).isRelative()) { QUrl u = QUrl::fromLocalFile(QDir::currentPath()); if (workingDirectory == QLatin1String(".")) workingDirectory = QString(); else if (workingDirectory.startsWith(QLatin1String("./"))) workingDirectory.remove(0, 2); if (!workingDirectory.isEmpty()) { u = u.adjusted(QUrl::StripTrailingSlash); u.setPath(u.path() + QLatin1Char('/') + workingDirectory); } workingDirectory = u.toLocalFile(); } else workingDirectory = _workingDirectory; ifTest(cout << "working-directory: " << workingDirectory.toLocal8Bit().data() << " file: " << file.toLocal8Bit().data() << std::endl;) QDir sourceDir(workingDirectory); QDir dir = QDir(mapToBuild(sourceDir.absolutePath())); QFileInfo makeFile(dir, QStringLiteral("Makefile")); if (!makeFile.exists()) { if (maxStepsUp > 0) { //If there is no makefile in this directory, go one up and re-try from there QFileInfo fileName(file); QString localName = sourceDir.dirName(); if (sourceDir.cdUp() && !fileName.isAbsolute()) { const QString checkFor = localName + QLatin1Char('/') + file; PathResolutionResult oneUp = resolveIncludePath(checkFor, sourceDir.path(), maxStepsUp-1); if (oneUp.success) { oneUp.mergeWith(resultOnFail); return oneUp; } } } if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Makefile is missing in folder \"%1\"", dir.absolutePath()), i18n("Problem while trying to resolve include paths for %1", file)); } PushValue e(m_isResolving, true); Path::List cachedPaths; //If the call doesn't succeed, use the cached not up-to-date version Path::List cachedFWDirs; QHash cachedDefines; ModificationRevisionSet dependency; dependency.addModificationRevision(IndexedString(makeFile.filePath()), ModificationRevision::revisionForFile(IndexedString(makeFile.filePath()))); dependency += resultOnFail.includePathDependency; Cache::iterator it; { QMutexLocker l(&s_cacheMutex); it = s_cache.find(dir.path()); if (it != s_cache.end()) { cachedPaths = it->paths; cachedFWDirs = it->frameworkDirectories; cachedDefines = it->defines; if (dependency == it->modificationTime) { if (!it->failed) { //We have a valid cached result PathResolutionResult ret(true); ret.paths = it->paths; ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //We have a cached failed result. We should use that for some time but then try again. Return the failed result if: (there were too many tries within this folder OR this file was already tried) AND The last tries have not expired yet if (/*(it->failedFiles.size() > 3 || it->failedFiles.find(file) != it->failedFiles.end()) &&*/ it->failTime.secsTo(QDateTime::currentDateTime()) < CACHE_FAIL_FOR_SECONDS) { PathResolutionResult ret(false); //Fake that the result is ok ret.errorMessage = i18n("Cached: %1", it->errorMessage); ret.longErrorMessage = it->longErrorMessage; ret.paths = it->paths; ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //Try getting a correct result again } } } } } ///STEP 1: Prepare paths QString targetName; QFileInfo fi(file); QString absoluteFile = file; if (fi.isRelative()) absoluteFile = workingDirectory + QLatin1Char('/') + file; absoluteFile = QDir::cleanPath(absoluteFile); int dot; if ((dot = file.lastIndexOf(QLatin1Char('.'))) == -1) { if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Filename %1 seems to be malformed", file)); } targetName = file.left(dot); QString wd = dir.path(); if (QFileInfo(wd).isRelative()) { wd = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + wd); } wd = mapToBuild(wd); SourcePathInformation source(wd); QStringList possibleTargets = source.possibleTargets(targetName); ///STEP 3: Try resolving the paths, by using once the absolute and once the relative file-path. Which kind is required differs from setup to setup. ///STEP 3.1: Try resolution using the absolute path PathResolutionResult res; //Try for each possible target res = resolveIncludePathInternal(absoluteFile, wd, possibleTargets.join(QLatin1Char(' ')), source, maximumInternalResolutionDepth); if (!res) { ifTest(cout << "Try for absolute file " << absoluteFile.toLocal8Bit().data() << " and targets " << possibleTargets.join(", ").toLocal8Bit().data() << " failed: " << res.longErrorMessage.toLocal8Bit().data() << endl;) } res.includePathDependency = dependency; if (res.paths.isEmpty()) { res.paths = cachedPaths; //We failed, maybe there is an old cached result, use that. res.defines = cachedDefines; } // a build command could contain only one or more -iframework or -F specifications. if (res.frameworkDirectories.isEmpty()) { res.frameworkDirectories = cachedFWDirs; } { QMutexLocker l(&s_cacheMutex); if (it == s_cache.end()) it = s_cache.insert(dir.path(), CacheEntry()); CacheEntry& ce(*it); ce.paths = res.paths; ce.frameworkDirectories = res.frameworkDirectories; ce.modificationTime = dependency; if (!res) { ce.failed = true; ce.errorMessage = res.errorMessage; ce.longErrorMessage = res.longErrorMessage; ce.failTime = QDateTime::currentDateTime(); ce.failedFiles[file] = true; } else { ce.failed = false; ce.failedFiles.clear(); } } if (!res && (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty())) return resultOnFail; return res; } static QRegularExpression includeRegularExpression() { - static const QRegularExpression expression(QLatin1String( + static const QRegularExpression expression(QStringLiteral( "\\s(--include-dir=|-I\\s*|-isystem\\s+|-iframework\\s+|-F\\s*)(" "\\'.*\\'|\\\".*\\\"" //Matches "hello", 'hello', 'hello"hallo"', etc. "|" "((?:\\\\.)?([\\S^\\\\]?))+" //Matches /usr/I\ am\ a\ strange\ path/include ")(?=\\s)" )); Q_ASSERT(expression.isValid()); return expression; } PathResolutionResult MakeFileResolver::resolveIncludePathInternal(const QString& file, const QString& workingDirectory, const QString& makeParameters, const SourcePathInformation& source, int maxDepth) { --maxDepth; if (maxDepth < 0) return PathResolutionResult(false); QString fullOutput; executeCommand(source.createCommand(file, workingDirectory, makeParameters), workingDirectory, fullOutput); { QRegExp newLineRx(QStringLiteral("\\\\\\n")); fullOutput.remove(newLineRx); } ///@todo collect multiple outputs at the same time for performance-reasons QString firstLine = fullOutput; int lineEnd; if ((lineEnd = fullOutput.indexOf(QLatin1Char('\n'))) != -1) firstLine.truncate(lineEnd); //Only look at the first line of output /** * There's two possible cases this can currently handle. * 1.: gcc is called, with the parameters we are searching for (so we parse the parameters) * 2.: A recursive make is called, within another directory(so we follow the recursion and try again) "cd /foo/bar && make -f pi/pa/build.make pi/pa/po.o * */ ///STEP 1: Test if it is a recursive make-call // Do not search for recursive make-calls if we already have include-paths available. Happens in kernel modules. if (!includeRegularExpression().match(fullOutput).hasMatch()) { QRegExp makeRx(QStringLiteral("\\bmake\\s")); int offset = 0; while ((offset = makeRx.indexIn(firstLine, offset)) != -1) { QString prefix = firstLine.left(offset).trimmed(); if (prefix.endsWith(QLatin1String("&&")) || prefix.endsWith(QLatin1Char(';')) || prefix.isEmpty()) { QString newWorkingDirectory = workingDirectory; ///Extract the new working-directory if (!prefix.isEmpty()) { if (prefix.endsWith(QLatin1String("&&"))) prefix.truncate(prefix.length() - 2); else if (prefix.endsWith(QLatin1Char(';'))) prefix.truncate(prefix.length() - 1); ///Now test if what we have as prefix is a simple "cd /foo/bar" call. //In cases like "cd /media/data/kdedev/4.0/build/kdevelop && cd /media/data/kdedev/4.0/build/kdevelop" //We use the second directory. For t hat reason we search for the last index of "cd " int cdIndex = prefix.lastIndexOf(QLatin1String("cd ")); if (cdIndex != -1) { newWorkingDirectory = prefix.mid(cdIndex + 3).trimmed(); if (QFileInfo(newWorkingDirectory).isRelative()) newWorkingDirectory = workingDirectory + QLatin1Char('/') + newWorkingDirectory; newWorkingDirectory = QDir::cleanPath(newWorkingDirectory); } } if (newWorkingDirectory == workingDirectory) { return PathResolutionResult(false, i18n("Failed to extract new working directory"), i18n("Output was: %1", fullOutput)); } QFileInfo d(newWorkingDirectory); if (d.exists()) { ///The recursive working-directory exists. QString makeParams = firstLine.mid(offset+5); if (!makeParams.contains(QLatin1Char(';')) && !makeParams.contains(QLatin1String("&&"))) { ///Looks like valid parameters ///Make the file-name absolute, so it can be referenced from any directory QString absoluteFile = file; if (QFileInfo(absoluteFile).isRelative()) absoluteFile = workingDirectory + QLatin1Char('/') + file; Path absolutePath(absoluteFile); ///Try once with absolute, and if that fails with relative path of the file SourcePathInformation newSource(newWorkingDirectory); PathResolutionResult res = resolveIncludePathInternal(absolutePath.toLocalFile(), newWorkingDirectory, makeParams, newSource, maxDepth); if (res) return res; return resolveIncludePathInternal(Path(newWorkingDirectory).relativePath(absolutePath), newWorkingDirectory, makeParams , newSource, maxDepth); }else{ return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The parameter string \"%1\" does not seem to be valid. Output was: %2.", makeParams, fullOutput)); } } else { return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The directory \"%1\" does not exist. Output was: %2.", newWorkingDirectory, fullOutput)); } } else { return PathResolutionResult(false, i18n("Malformed recursive make call"), i18n("Output was: %1", fullOutput)); } ++offset; if (offset >= firstLine.length()) break; } } ///STEP 2: Search the output for include-paths PathResolutionResult ret = processOutput(fullOutput, workingDirectory); if (ret.paths.isEmpty() && ret.frameworkDirectories.isEmpty()) return PathResolutionResult(false, i18n("Could not extract include paths from make output"), i18n("Folder: \"%1\" Command: \"%2\" Output: \"%3\"", workingDirectory, source.createCommand(file, workingDirectory, makeParameters), fullOutput)); return ret; } QRegularExpression MakeFileResolver::defineRegularExpression() { static const QRegularExpression pattern( QStringLiteral("-D([^\\s=]+)(?:=(?:\"(.*?)(? 2)) { //probable a quoted path if (path.endsWith(path.leftRef(1))) { //Quotation is ok, remove it path = path.mid(1, path.length() - 2); } } if (QDir::isRelativePath(path)) path = workingDirectory + QLatin1Char('/') + path; const auto& internedPath = internPath(path); const auto& type = match.captured(1); const auto isFramework = type.startsWith(QLatin1String("-iframework")) || type.startsWith(QLatin1String("-F")); if (isFramework) { ret.frameworkDirectories << internedPath; } else { ret.paths << internedPath; } } } { const auto& defineRx = defineRegularExpression(); auto it = defineRx.globalMatch(fullOutput); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret.defines[internString(match.captured(1))] = internString(value); } } return ret; } void MakeFileResolver::resetOutOfSourceBuild() { m_outOfSource = false; } void MakeFileResolver::setOutOfSourceBuildSystem(const QString& source, const QString& build) { if (source == build) { resetOutOfSourceBuild(); return; } m_outOfSource = true; m_source = QDir::cleanPath(source); m_build = QDir::cleanPath(m_build); } Path MakeFileResolver::internPath(const QString& path) const { Path& ret = m_pathCache[path]; if (ret.isEmpty() != path.isEmpty()) { ret = Path(path); } return ret; } QString MakeFileResolver::internString(const QString& path) const { auto it = m_stringCache.constFind(path); if (it != m_stringCache.constEnd()) { return *it; } m_stringCache.insert(path); return path; } // kate: indent-width 2; tab-width 2; diff --git a/plugins/customscript/customscript_plugin.cpp b/plugins/customscript/customscript_plugin.cpp index a23bcf63ea..ac4686696d 100644 --- a/plugins/customscript/customscript_plugin.cpp +++ b/plugins/customscript/customscript_plugin.cpp @@ -1,567 +1,567 @@ /* This file is part of KDevelop Copyright (C) 2008 Cédric Pasteur Copyright (C) 2011 David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "customscript_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; static QPointer indentPluginSingleton; K_PLUGIN_FACTORY_WITH_JSON(CustomScriptFactory, "kdevcustomscript.json", registerPlugin(); ) // Replaces ${KEY} in command with variables[KEY] static QString replaceVariables(QString command, QMap variables) { while (command.contains(QLatin1String("${"))) { int pos = command.indexOf(QLatin1String("${")); int end = command.indexOf(QLatin1Char('}'), pos + 2); if (end == -1) { break; } QString key = command.mid(pos + 2, end - pos - 2); if (variables.contains(key)) { command.replace(pos, 1 + end - pos, variables[key]); } else { qCDebug(CUSTOMSCRIPT) << "found no variable while replacing in shell-command" << command << "key" << key << "available:" << variables; command.remove(pos, 1 + end - pos); } } return command; } CustomScriptPlugin::CustomScriptPlugin(QObject* parent, const QVariantList&) : IPlugin(QStringLiteral("kdevcustomscript"), parent) { m_currentStyle = predefinedStyles().at(0); indentPluginSingleton = this; } CustomScriptPlugin::~CustomScriptPlugin() { } QString CustomScriptPlugin::name() const { // This needs to match the X-KDE-PluginInfo-Name entry from the .desktop file! return QStringLiteral("kdevcustomscript"); } QString CustomScriptPlugin::caption() const { return QStringLiteral("Custom Script Formatter"); } QString CustomScriptPlugin::description() const { return i18n("Indent and Format Source Code.
" "This plugin allows using powerful external formatting tools " "that can be invoked through the command-line.
" "For example, the uncrustify, astyle or indent " "formatters can be used.
" "The advantage of command-line formatters is that formatting configurations " "can be easily shared by all team members, independent of their preferred IDE."); } QString CustomScriptPlugin::formatSourceWithStyle(SourceFormatterStyle style, const QString& text, const QUrl& url, const QMimeType& /*mime*/, const QString& leftContext, const QString& rightContext) const { KProcess proc; QTextStream ios(&proc); std::unique_ptr tmpFile; if (style.content().isEmpty()) { style = predefinedStyle(style.name()); if (style.content().isEmpty()) { qCWarning(CUSTOMSCRIPT) << "Empty contents for style" << style.name() << "for indent plugin"; return text; } } QString useText = text; useText = leftContext + useText + rightContext; QMap projectVariables; foreach (IProject* project, ICore::self()->projectController()->projects()) { projectVariables[project->name()] = project->path().toUrl().toLocalFile(); } QString command = style.content(); // Replace ${Project} with the project path command = replaceVariables(command, projectVariables); command.replace(QLatin1String("$FILE"), url.toLocalFile()); if (command.contains(QLatin1String("$TMPFILE"))) { tmpFile.reset(new QTemporaryFile(QDir::tempPath() + QLatin1String("/code"))); tmpFile->setAutoRemove(false); if (tmpFile->open()) { qCDebug(CUSTOMSCRIPT) << "using temporary file" << tmpFile->fileName(); command.replace(QLatin1String("$TMPFILE"), tmpFile->fileName()); QByteArray useTextArray = useText.toLocal8Bit(); if (tmpFile->write(useTextArray) != useTextArray.size()) { qCWarning(CUSTOMSCRIPT) << "failed to write text to temporary file"; return text; } } else { qCWarning(CUSTOMSCRIPT) << "Failed to create a temporary file"; return text; } tmpFile->close(); } qCDebug(CUSTOMSCRIPT) << "using shell command for indentation: " << command; proc.setShellCommand(command); proc.setOutputChannelMode(KProcess::OnlyStdoutChannel); proc.start(); if (!proc.waitForStarted()) { qCDebug(CUSTOMSCRIPT) << "Unable to start indent" << endl; return text; } if (!tmpFile.get()) { proc.write(useText.toLocal8Bit()); } proc.closeWriteChannel(); if (!proc.waitForFinished()) { qCDebug(CUSTOMSCRIPT) << "Process doesn't finish" << endl; return text; } QString output; if (tmpFile.get()) { QFile f(tmpFile->fileName()); if (f.open(QIODevice::ReadOnly)) { output = QString::fromLocal8Bit(f.readAll()); } else { qCWarning(CUSTOMSCRIPT) << "Failed opening the temporary file for reading"; return text; } } else { output = ios.readAll(); } if (output.isEmpty()) { qCWarning(CUSTOMSCRIPT) << "indent returned empty text for style" << style.name() << style.content(); return text; } int tabWidth = 4; if ((!leftContext.isEmpty() || !rightContext.isEmpty()) && (text.contains(QLatin1Char(' ')) || output.contains(QLatin1Char('\t')))) { // If we have to do contex-matching with tabs, determine the correct tab-width so that the context // can be matched correctly Indentation indent = indentation(url); if (indent.indentationTabWidth > 0) { tabWidth = indent.indentationTabWidth; } } return KDevelop::extractFormattedTextFromContext(output, text, leftContext, rightContext, tabWidth); } QString CustomScriptPlugin::formatSource(const QString& text, const QUrl& url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const { auto style = KDevelop::ICore::self()->sourceFormatterController()->styleForUrl(url, mime); return formatSourceWithStyle(style, text, url, mime, leftContext, rightContext); } static QVector stylesFromLanguagePlugins() { QVector styles; foreach (auto lang, ICore::self()->languageController()->loadedLanguages()) { const SourceFormatterItemList& languageStyles = lang->sourceFormatterItems(); for (const SourceFormatterStyleItem& item: languageStyles) { if (item.engine == QLatin1String("customscript")) { styles << item.style; } } } return styles; } KDevelop::SourceFormatterStyle CustomScriptPlugin::predefinedStyle(const QString& name) const { const auto& langStyles = stylesFromLanguagePlugins(); for (auto& langStyle: langStyles) { qCDebug(CUSTOMSCRIPT) << "looking at style from language with custom sample" << langStyle.description() << langStyle.overrideSample(); if (langStyle.name() == name) { return langStyle; } } SourceFormatterStyle result(name); if (name == QLatin1String("GNU_indent_GNU")) { result.setCaption(i18n("Gnu Indent: GNU")); result.setContent(QStringLiteral("indent")); result.setUsePreview(true); } else if (name == QLatin1String("GNU_indent_KR")) { result.setCaption(i18n("Gnu Indent: Kernighan & Ritchie")); result.setContent(QStringLiteral("indent -kr")); result.setUsePreview(true); } else if (name == QLatin1String("GNU_indent_orig")) { result.setCaption(i18n("Gnu Indent: Original Berkeley indent style")); result.setContent(QStringLiteral("indent -orig")); result.setUsePreview(true); } else if (name == QLatin1String("kdev_format_source")) { result.setCaption(QStringLiteral("KDevelop: kdev_format_source")); result.setContent(QStringLiteral("kdev_format_source $FILE $TMPFILE")); result.setUsePreview(false); result.setDescription(i18n("Description:
" "kdev_format_source is a script bundled with KDevelop " "which allows using fine-grained formatting rules by placing " "meta-files called format_sources into the file-system.

" "Each line of the format_sources files defines a list of wildcards " "followed by a colon and the used formatting-command.

" "The formatting-command should use $TMPFILE to reference the " "temporary file to reformat.

" "Example:
" "*.cpp *.h : myformatter $TMPFILE
" "This will reformat all files ending with .cpp or .h using " "the custom formatting script myformatter.

" "Example:
" "subdir/* : uncrustify -l CPP -f $TMPFILE -c uncrustify.config -o $TMPFILE
" "This will reformat all files in subdirectory subdir using the uncrustify " "tool with the config-file uncrustify.config.")); } result.setMimeTypes({ {QStringLiteral("text/x-c++src"), QStringLiteral("C++")}, {QStringLiteral("text/x-chdr"), QStringLiteral("C")}, {QStringLiteral("text/x-c++hdr"), QStringLiteral("C++")}, {QStringLiteral("text/x-csrc"), QStringLiteral("C")}, {QStringLiteral("text/x-java"), QStringLiteral("Java")}, {QStringLiteral("text/x-csharp"), QStringLiteral("C#")} }); return result; } QVector CustomScriptPlugin::predefinedStyles() const { const QVector styles = stylesFromLanguagePlugins() + QVector{ predefinedStyle(QStringLiteral("kdev_format_source")), predefinedStyle(QStringLiteral("GNU_indent_GNU")), predefinedStyle(QStringLiteral("GNU_indent_KR")), predefinedStyle(QStringLiteral("GNU_indent_orig")), }; return styles; } KDevelop::SettingsWidget* CustomScriptPlugin::editStyleWidget(const QMimeType& mime) const { Q_UNUSED(mime); return new CustomScriptPreferences(); } static QString formattingSample() { - return QLatin1String( + return QStringLiteral( "// Formatting\n" "void func(){\n" "\tif(isFoo(a,b))\n" "\tbar(a,b);\n" "if(isFoo)\n" "\ta=bar((b-c)*a,*d--);\n" "if( isFoo( a,b ) )\n" "\tbar(a, b);\n" "if (isFoo) {isFoo=false;cat << isFoo <::const_iterator it = list.begin();\n" "}\n" "namespace A {\n" "namespace B {\n" "void foo() {\n" " if (true) {\n" " func();\n" " } else {\n" " // bla\n" " }\n" "}\n" "}\n" "}\n"); } static QString indentingSample() { - return QLatin1String( + return QStringLiteral( "// Indentation\n" "#define foobar(A)\\\n" "{Foo();Bar();}\n" "#define anotherFoo(B)\\\n" "return Bar()\n" "\n" "namespace Bar\n" "{\n" "class Foo\n" "{public:\n" "Foo();\n" "virtual ~Foo();\n" "};\n" "void bar(int foo)\n" "{\n" "switch (foo)\n" "{\n" "case 1:\n" "a+=1;\n" "break;\n" "case 2:\n" "{\n" "a += 2;\n" " break;\n" "}\n" "}\n" "if (isFoo)\n" "{\n" "bar();\n" "}\n" "else\n" "{\n" "anotherBar();\n" "}\n" "}\n" "int foo()\n" "\twhile(isFoo)\n" "\t\t{\n" "\t\t\t// ...\n" "\t\t\tgoto error;\n" "\t\t/* .... */\n" "\t\terror:\n" "\t\t\t//...\n" "\t\t}\n" "\t}\n" "fooArray[]={ red,\n" "\tgreen,\n" "\tdarkblue};\n" "fooFunction(barArg1,\n" "\tbarArg2,\n" "\tbarArg3);\n" ); } QString CustomScriptPlugin::previewText(const SourceFormatterStyle& style, const QMimeType& /*mime*/) const { if (!style.overrideSample().isEmpty()) { return style.overrideSample(); } return formattingSample() + QLatin1String("\n\n") + indentingSample(); } QStringList CustomScriptPlugin::computeIndentationFromSample(const QUrl& url) const { QStringList ret; auto languages = ICore::self()->languageController()->languagesForUrl(url); if (languages.isEmpty()) { return ret; } QString sample = languages[0]->indentationSample(); QString formattedSample = formatSource(sample, url, QMimeDatabase().mimeTypeForUrl(url), QString(), QString()); const QStringList lines = formattedSample.split(QLatin1Char('\n')); for (const QString& line : lines) { if (!line.isEmpty() && line[0].isSpace()) { QString indent; for (const QChar c : line) { if (c.isSpace()) { indent.push_back(c); } else { break; } } if (!indent.isEmpty() && !ret.contains(indent)) { ret.push_back(indent); } } } return ret; } CustomScriptPlugin::Indentation CustomScriptPlugin::indentation(const QUrl& url) const { Indentation ret; QStringList indent = computeIndentationFromSample(url); if (indent.isEmpty()) { qCDebug(CUSTOMSCRIPT) << "failed extracting a valid indentation from sample for url" << url; return ret; // No valid indentation could be extracted } if (indent[0].contains(QLatin1Char(' '))) { ret.indentWidth = indent[0].count(QLatin1Char(' ')); } if (!indent.join(QString()).contains(QLatin1Char(' '))) { ret.indentationTabWidth = -1; // Tabs are not used for indentation } if (indent[0] == QLatin1String(" ")) { // The script indents with tabs-only // The problem is that we don't know how // wide a tab is supposed to be. // // We need indentation-width=tab-width // to make the editor do tab-only formatting, // so choose a random with of 4. ret.indentWidth = 4; ret.indentationTabWidth = 4; } else if (ret.indentWidth) { // Tabs are used for indentation, alongside with spaces // Try finding out how many spaces one tab stands for. // Do it by assuming a uniform indentation-step with each level. for (int pos = 0; pos < indent.size(); ++pos) { if (indent[pos] == QLatin1String(" ")&& pos >= 1) { // This line consists of only a tab. int prevWidth = indent[pos - 1].length(); int prevPrevWidth = (pos >= 2) ? indent[pos - 2].length() : 0; int step = prevWidth - prevPrevWidth; qCDebug(CUSTOMSCRIPT) << "found in line " << pos << prevWidth << prevPrevWidth << step; if (step > 0 && step <= prevWidth) { qCDebug(CUSTOMSCRIPT) << "Done"; ret.indentationTabWidth = prevWidth + step; break; } } } } qCDebug(CUSTOMSCRIPT) << "indent-sample" << QLatin1Char('\"') + indent.join(QLatin1Char('\n')) + QLatin1Char('\"') << "extracted tab-width" << ret.indentationTabWidth << "extracted indentation width" << ret.indentWidth; return ret; } void CustomScriptPreferences::updateTimeout() { const QString& text = indentPluginSingleton.data()->previewText(m_style, QMimeType()); QString formatted = indentPluginSingleton.data()->formatSourceWithStyle(m_style, text, QUrl(), QMimeType()); emit previewTextChanged(formatted); } CustomScriptPreferences::CustomScriptPreferences() { m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); m_updateTimer->setInterval(1000); connect(m_updateTimer, &QTimer::timeout, this, &CustomScriptPreferences::updateTimeout); m_vLayout = new QVBoxLayout(this); m_vLayout->setMargin(0); m_captionLabel = new QLabel; m_vLayout->addWidget(m_captionLabel); m_vLayout->addSpacing(10); m_hLayout = new QHBoxLayout; m_vLayout->addLayout(m_hLayout); m_commandLabel = new QLabel; m_hLayout->addWidget(m_commandLabel); m_commandEdit = new QLineEdit; m_hLayout->addWidget(m_commandEdit); m_commandLabel->setText(i18n("Command:")); m_vLayout->addSpacing(10); m_bottomLabel = new QLabel; m_vLayout->addWidget(m_bottomLabel); m_bottomLabel->setTextFormat(Qt::RichText); m_bottomLabel->setText( i18n("You can enter an arbitrary shell command.
" "The unformatted source-code is reached to the command
" "through the standard input, and the
" "formatted result is read from the standard output.
" "
" "If you add $TMPFILE into the command, then
" "a temporary file is used for transferring the code.")); connect(m_commandEdit, &QLineEdit::textEdited, m_updateTimer, static_cast(&QTimer::start)); m_vLayout->addSpacing(10); m_moreVariablesButton = new QPushButton(i18n("More Variables")); connect(m_moreVariablesButton, &QPushButton::clicked, this, &CustomScriptPreferences::moreVariablesClicked); m_vLayout->addWidget(m_moreVariablesButton); m_vLayout->addStretch(); } void CustomScriptPreferences::load(const KDevelop::SourceFormatterStyle& style) { m_style = style; m_commandEdit->setText(style.content()); m_captionLabel->setText(i18n("Style: %1", style.caption())); updateTimeout(); } QString CustomScriptPreferences::save() { return m_commandEdit->text(); } void CustomScriptPreferences::moreVariablesClicked(bool) { KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("$TMPFILE will be replaced with the path to a temporary file.
" "The code will be written into the file, the temporary
" "file will be substituted into that position, and the result
" "will be read out of that file.
" "
" "$FILE will be replaced with the path of the original file.
" "The contents of the file must not be modified, changes are allowed
" "only in $TMPFILE.
" "
" "${PROJECT_NAME} will be replaced by the path of
" "the currently open project with the matching name." ), i18n("Variable Replacements")); } #include "customscript_plugin.moc" // kate: indent-mode cstyle; space-indent off; tab-width 4;