File indexing completed on 2024-04-28 17:06:31

0001 /*
0002     SPDX-FileCopyrightText: 2004 Jonas Bähr <jonas.baehr@web.de>
0003     SPDX-FileCopyrightText: 2004 Shie Erlich <erlich@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "expander.h"
0010 
0011 #include "../FileSystem/filesystemprovider.h"
0012 #include "../GUI/profilemanager.h"
0013 #include "../KViewer/krviewer.h"
0014 #include "../Panel/PanelView/krview.h"
0015 #include "../Panel/listpanel.h"
0016 #include "../Panel/panelfunc.h"
0017 #include "../Search/krsearchdialog.h"
0018 #include "../krservices.h"
0019 #include "../krusader.h"
0020 #include "../krusaderview.h"
0021 #include "../panelmanager.h"
0022 
0023 #ifdef SYNCHRONIZER_ENABLED
0024 #include "../Synchronizer/synchronizergui.h"
0025 #endif
0026 
0027 // QtCore
0028 #include <QDebug>
0029 #include <QList>
0030 #include <QStringList>
0031 #include <QTemporaryFile>
0032 #include <QTextStream>
0033 // QtGui
0034 #include <QClipboard>
0035 // QtWidgets
0036 #include <QApplication>
0037 #include <QInputDialog>
0038 
0039 #include <KI18n/KLocalizedString>
0040 #include <KWidgetsAddons/KMessageBox>
0041 
0042 #include <algorithm>
0043 #include <functional>
0044 using namespace std;
0045 
0046 #define NEED_PANEL                                                                                                                                             \
0047     if (panel == nullptr) {                                                                                                                                    \
0048         panelMissingError(_expression, exp);                                                                                                                   \
0049         return QString();                                                                                                                                      \
0050     }
0051 
0052 inline void exp_placeholder::setError(Expander &exp, const Error &e)
0053 {
0054     exp.setError(e);
0055 }
0056 inline QStringList exp_placeholder::splitEach(const TagString &s)
0057 {
0058     return Expander::splitEach(s);
0059 }
0060 inline exp_placeholder::exp_placeholder() = default;
0061 
0062 void exp_placeholder::panelMissingError(const QString &s, Expander &exp)
0063 {
0064     exp.setError(Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Needed panel specification missing in expander %1", s)));
0065 }
0066 
0067 QStringList exp_placeholder::fileList(const KrPanel *const panel,
0068                                       const QString &type,
0069                                       const QString &mask,
0070                                       const bool omitPath,
0071                                       const bool useUrl,
0072                                       Expander &exp,
0073                                       const QString &error)
0074 {
0075     QStringList items;
0076     if (type.isEmpty() || type == "all")
0077         panel->view->getItemsByMask(mask, &items);
0078     else if (type == "files")
0079         panel->view->getItemsByMask(mask, &items, false, true);
0080     else if (type == "dirs")
0081         panel->view->getItemsByMask(mask, &items, true, false);
0082     else if (type == "selected")
0083         panel->view->getSelectedItems(&items);
0084     else {
0085         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: Bad argument to %1: %2 is not valid item specifier", error, type)));
0086         return QStringList();
0087     }
0088     if (!omitPath) { // add the current path
0089         // translate to urls using filesystem
0090         QList<QUrl> list = panel->func->files()->getUrls(items);
0091         items.clear();
0092         // parse everything to a single qstring
0093         foreach (const QUrl &url, list) {
0094             items.push_back(useUrl ? url.url() : url.path());
0095         }
0096     }
0097 
0098     return items;
0099 }
0100 
0101 namespace
0102 {
0103 
0104 class exp_simpleplaceholder : public exp_placeholder
0105 {
0106 public:
0107     EXP_FUNC override;
0108     virtual TagString expFunc(const KrPanel *, const QStringList &, const bool &, Expander &) const = 0;
0109 };
0110 
0111 #define PLACEHOLDER_CLASS(name)                                                                                                                                \
0112     class name : public exp_placeholder                                                                                                                        \
0113     {                                                                                                                                                          \
0114     public:                                                                                                                                                    \
0115         name();                                                                                                                                                \
0116         virtual TagString expFunc(const KrPanel *, const TagStringList &, const bool &, Expander &) const override;                                            \
0117     };
0118 
0119 #define SIMPLE_PLACEHOLDER_CLASS(name)                                                                                                                         \
0120     class name : public exp_simpleplaceholder                                                                                                                  \
0121     {                                                                                                                                                          \
0122     public:                                                                                                                                                    \
0123         using exp_simpleplaceholder::expFunc;                                                                                                                  \
0124         name();                                                                                                                                                \
0125         virtual TagString expFunc(const KrPanel *, const QStringList &, const bool &, Expander &) const override;                                              \
0126     };
0127 
0128 /**
0129  * expands %_Path% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the path of the specified panel
0130  */
0131 SIMPLE_PLACEHOLDER_CLASS(exp_Path)
0132 
0133 /**
0134  * expands %_Count% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the number of items, which type is
0135  * specified by the first Parameter
0136  */
0137 SIMPLE_PLACEHOLDER_CLASS(exp_Count)
0138 
0139 /**
0140  * expands %_Filter% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the correspondent filter (ie: "*.cpp")
0141  */
0142 SIMPLE_PLACEHOLDER_CLASS(exp_Filter)
0143 
0144 /**
0145  * expands %_Current% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with the current item ( != the selected ones)
0146  */
0147 SIMPLE_PLACEHOLDER_CLASS(exp_Current)
0148 
0149 /**
0150  * expands %_List% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate the active, other, right or left panel) with a list of items, which type is specified by
0151  * the first Parameter
0152  */
0153 SIMPLE_PLACEHOLDER_CLASS(exp_List)
0154 
0155 /**
0156  * expands %_ListFile% ('_' is replaced by 'a', 'o', 'r' or 'l' to indicate
0157  * the active, other, right or left panel) with the name of a temporary file,
0158  * containing a list of items, which type is specified by the first Parameter
0159  */
0160 SIMPLE_PLACEHOLDER_CLASS(exp_ListFile)
0161 
0162 /**
0163  * expands %_Ask% ('_' is necessary because there is no panel needed)
0164  * with the return of an input-dialog
0165  */
0166 SIMPLE_PLACEHOLDER_CLASS(exp_Ask)
0167 
0168 /**
0169  * This copies it's first Parameter to the clipboard
0170  */
0171 PLACEHOLDER_CLASS(exp_Clipboard)
0172 
0173 /**
0174  * This selects all items by the mask given with the first Parameter
0175  */
0176 SIMPLE_PLACEHOLDER_CLASS(exp_Select)
0177 
0178 /**
0179  * This changes the panel'spath to the value given with the first Parameter.
0180  */
0181 SIMPLE_PLACEHOLDER_CLASS(exp_Goto)
0182 
0183 /**
0184  * This is equal to 'cp <first Parameter> <second Parameter>'.
0185  */
0186 PLACEHOLDER_CLASS(exp_Copy)
0187 
0188 /**
0189  * This is equal to 'mv <first Parameter> <second Parameter>'.
0190  */
0191 PLACEHOLDER_CLASS(exp_Move)
0192 
0193 #ifdef SYNCHRONIZER_ENABLED
0194 /**
0195  * This opens the synchronizer with a given profile
0196  */
0197 SIMPLE_PLACEHOLDER_CLASS(exp_Sync)
0198 #endif
0199 
0200 /**
0201  * This opens the searchmodule with a given profile
0202  */
0203 SIMPLE_PLACEHOLDER_CLASS(exp_NewSearch)
0204 
0205 /**
0206  * This loads the panel-profile with a given name
0207  */
0208 SIMPLE_PLACEHOLDER_CLASS(exp_Profile)
0209 
0210 /**
0211  * This is setting marks in the string where he is later split up for each {all, selected, files, dirs}
0212  */
0213 SIMPLE_PLACEHOLDER_CLASS(exp_Each)
0214 
0215 /**
0216  * This sets the sorting on a specific column
0217  */
0218 SIMPLE_PLACEHOLDER_CLASS(exp_ColSort)
0219 
0220 /**
0221  * This sets relation between the left and right panel
0222  */
0223 SIMPLE_PLACEHOLDER_CLASS(exp_PanelSize)
0224 
0225 /**
0226  * This loads a file in the internal viewer
0227  */
0228 SIMPLE_PLACEHOLDER_CLASS(exp_View)
0229 
0230 ////////////////////////////////////////////////////////////
0231 //////////////////////// utils ////////////////////////
0232 ////////////////////////////////////////////////////////////
0233 
0234 /**
0235  * escapes everything that confuses bash in filenames
0236  * @param s String to manipulate
0237  * @return escaped string
0238  */
0239 QString bashquote(QString s)
0240 {
0241     /*
0242     // we _can_not_ use this function because it _encloses_ the sting in single-quotes!
0243     // In this case quotes strings could not be concatenated anymore
0244     return KrServices::quote(s);
0245     */
0246 
0247     static const QString evilstuff = "\\\"'`()[]{}!?;$&<>| \t\r\n"; // stuff that should get escaped
0248 
0249     for (auto i : evilstuff)
0250         s.replace(i, ('\\' + i));
0251 
0252     return s;
0253 }
0254 
0255 QString separateAndQuote(QStringList list, const QString &separator, const bool quote)
0256 {
0257     if (quote)
0258         transform(list.begin(), list.end(), list.begin(), bashquote);
0259 
0260     // QLineEdit::text() always escapes special characters, revert this for newline and tab
0261     QString decodedSeparator = separator;
0262     decodedSeparator.replace("\\n", "\n").replace("\\t", "\t");
0263     return list.join(decodedSeparator);
0264 }
0265 /////////////////////////////////////////////////////////////////////////////////////////////////
0266 /////////////////////////////////// expander classes ////////////////////////////////////
0267 /////////////////////////////////////////////////////////////////////////////////////////////////
0268 
0269 exp_Path::exp_Path()
0270 {
0271     _expression = "Path";
0272     _description = i18n("Panel's Path...");
0273     _needPanel = true;
0274 
0275     addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false));
0276 }
0277 TagString exp_Path::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &useUrl, Expander &exp) const
0278 {
0279     NEED_PANEL
0280 
0281     QString result;
0282 
0283     if (useUrl)
0284         result = panel->func->files()->currentDirectory().url() + '/';
0285     else
0286         result = panel->func->files()->currentDirectory().path() + '/';
0287 
0288     if (parameter.count() > 0 && parameter[0].toLower() == "no") // don't escape spaces
0289         return TagString(result);
0290     else
0291         return TagString(bashquote(result));
0292 }
0293 
0294 exp_Count::exp_Count()
0295 {
0296     _expression = "Count";
0297     _description = i18n("Number of...");
0298     _needPanel = true;
0299 
0300     addParameter(exp_parameter(i18n("Count:"), "__choose:All;Files;Dirs;Selected", false));
0301 }
0302 TagString exp_Count::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &, Expander &exp) const
0303 {
0304     NEED_PANEL
0305 
0306     int n = -1;
0307     if (parameter.count() == 0 || parameter[0].isEmpty() || parameter[0].toLower() == "all")
0308         n = panel->view->numDirs() + panel->view->numFiles();
0309     else if (parameter[0].toLower() == "files")
0310         n = panel->view->numFiles();
0311     else if (parameter[0].toLower() == "dirs")
0312         n = panel->view->numDirs();
0313     else if (parameter[0].toLower() == "selected")
0314         n = panel->view->numSelected();
0315     else {
0316         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: Bad argument to Count: %1 is not valid item specifier", parameter[0])));
0317         return QString();
0318     }
0319 
0320     return TagString(QString("%1").arg(n));
0321 }
0322 
0323 exp_Filter::exp_Filter()
0324 {
0325     _expression = "Filter";
0326     _description = i18n("Filter Mask (*.h, *.cpp, etc.)");
0327     _needPanel = true;
0328 }
0329 TagString exp_Filter::expFunc(const KrPanel *panel, const QStringList &, const bool &, Expander &exp) const
0330 {
0331     NEED_PANEL
0332 
0333     return panel->view->filterMask().nameFilter();
0334 }
0335 
0336 exp_Current::exp_Current()
0337 {
0338     _expression = "Current";
0339     _description = i18n("Current File (!= Selected File)...");
0340     _needPanel = true;
0341 
0342     addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false));
0343     addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false));
0344 }
0345 TagString exp_Current::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &useUrl, Expander &exp) const
0346 {
0347     NEED_PANEL
0348 
0349     QString item = panel->view->getCurrentItem();
0350     if (item == "..") {
0351         // if ".." is current, treat this as nothing is current
0352         return QString();
0353     }
0354 
0355     QString result;
0356     if (parameter.count() > 0 && parameter[0].toLower() == "yes") // omit the current path
0357         result = item;
0358     else {
0359         const QUrl url = panel->func->files()->getUrl(item);
0360         result = useUrl ? url.url() : url.path();
0361     }
0362 
0363     const bool escapeSpaces = parameter.count() < 2 || parameter[1].toLower() != "no";
0364     return escapeSpaces ? bashquote(result) : result;
0365 }
0366 
0367 exp_List::exp_List()
0368 {
0369     _expression = "List";
0370     _description = i18n("Item List of...");
0371     _needPanel = true;
0372 
0373     addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false));
0374     addParameter(exp_parameter(i18n("Separator between the items (optional):"), " ", false));
0375     addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false));
0376     addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false));
0377     addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false));
0378 }
0379 TagString exp_List::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &useUrl, Expander &exp) const
0380 {
0381     NEED_PANEL
0382 
0383     // get selected items from view
0384     QStringList items;
0385     QString mask;
0386 
0387     if (parameter.count() <= 3 || parameter[3].isEmpty())
0388         mask = '*';
0389     else
0390         mask = parameter[3];
0391 
0392     return separateAndQuote(fileList(panel,
0393                                      parameter.isEmpty() ? QString() : parameter[0].toLower(),
0394                                      mask,
0395                                      parameter.count() > 2 ? parameter[2].toLower() == "yes" : false,
0396                                      useUrl,
0397                                      exp,
0398                                      "List"),
0399                             parameter.count() > 1 ? parameter[1] : " ",
0400                             parameter.count() > 4 ? parameter[4].toLower() == "yes" : true);
0401 }
0402 
0403 exp_ListFile::exp_ListFile()
0404 {
0405     _expression = "ListFile";
0406     _description = i18n("Filename of an Item List...");
0407     _needPanel = true;
0408 
0409     addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false));
0410     addParameter(exp_parameter(i18n("Separator between the items (optional)"), "\n", false));
0411     addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false));
0412     addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false));
0413     addParameter(exp_parameter(i18n("Automatically escape spaces"), "__no", false));
0414 }
0415 TagString exp_ListFile::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &useUrl, Expander &exp) const
0416 {
0417     NEED_PANEL
0418 
0419     // get selected items from view
0420     QStringList items;
0421     QString mask;
0422 
0423     if (parameter.count() <= 3 || parameter[3].isEmpty())
0424         mask = '*';
0425     else
0426         mask = parameter[3];
0427     QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.itemlist"));
0428     tmpFile.setAutoRemove(false);
0429 
0430     if (!tmpFile.open()) {
0431         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_WORLD, i18n("Expander: temporary file could not be opened (%1)", tmpFile.errorString())));
0432         return QString();
0433     }
0434 
0435     QTextStream stream(&tmpFile);
0436     stream << separateAndQuote(fileList(panel,
0437                                         parameter.isEmpty() ? QString() : parameter[0].toLower(),
0438                                         mask,
0439                                         parameter.count() > 2 ? parameter[2].toLower() == "yes" : false,
0440                                         useUrl,
0441                                         exp,
0442                                         "ListFile"),
0443                                parameter.count() > 1 ? parameter[1] : "\n",
0444                                parameter.count() > 4 ? parameter[4].toLower() == "yes" : true)
0445            << "\n";
0446     tmpFile.close();
0447 
0448     return tmpFile.fileName();
0449 }
0450 
0451 exp_Select::exp_Select()
0452 {
0453     _expression = "Select";
0454     _description = i18n("Manipulate the Selection...");
0455     _needPanel = true;
0456 
0457     addParameter(exp_parameter(i18n("Selection mask:"), "__select", true));
0458     addParameter(exp_parameter(i18n("Manipulate in which way:"), "__choose:Set;Add;Remove", false));
0459 }
0460 TagString exp_Select::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &, Expander &exp) const
0461 {
0462     NEED_PANEL
0463 
0464     KrQuery mask;
0465     if (parameter.count() <= 0 || parameter[0].isEmpty())
0466         mask = KrQuery("*");
0467     else
0468         mask = KrQuery(parameter[0]);
0469 
0470     if (parameter.count() > 1 && parameter[1].toLower() == "list-add")
0471         panel->view->select(mask);
0472     else if (parameter.count() > 1 && parameter[1].toLower() == "list-remove")
0473         panel->view->unselect(mask);
0474     else { // parameter[1].toLower() == "set" or isEmpty() or whatever
0475         panel->view->unselect(KrQuery("*"));
0476         panel->view->select(mask);
0477     }
0478 
0479     return QString(); // this doesn't return anything, that's normal!
0480 }
0481 
0482 exp_Goto::exp_Goto()
0483 {
0484     _expression = "Goto";
0485     _description = i18n("Jump to a Location...");
0486     _needPanel = true;
0487 
0488     addParameter(exp_parameter(i18n("Choose a path:"), "__goto", true));
0489     addParameter(exp_parameter(i18n("Open location in a new tab"), "__no", false));
0490 }
0491 TagString exp_Goto::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &, Expander &exp) const
0492 {
0493     NEED_PANEL
0494 
0495     bool newTab = false;
0496     if (parameter.count() > 1 && parameter[1].toLower() == "yes")
0497         newTab = true;
0498 
0499     if (parameter.count() == 0) {
0500         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Goto.")));
0501         return QString();
0502     }
0503 
0504     QUrl url = QUrl::fromUserInput(parameter[0], QString(), QUrl::AssumeLocalFile);
0505     if (newTab) {
0506         if (panel == LEFT_PANEL)
0507             MAIN_VIEW->leftManager()->slotNewTab(url);
0508         else
0509             MAIN_VIEW->rightManager()->slotNewTab(url);
0510     } else {
0511         panel->func->openUrl(url, "");
0512         panel->gui->slotFocusOnMe();
0513     }
0514 
0515     return QString(); // this doesn't return anything, that's normal!
0516 }
0517 
0518 /*
0519 exp_Search::exp_Search() {
0520    _expression = "Search";
0521    _description = i18n("Search for files");
0522    _needPanel = true;
0523 
0524    addParameter( new exp_parameter( i18n("please choose the setting"), "__searchprofile", true ) );
0525    addParameter( new exp_parameter( i18n("open the search in a new tab"), "__yes", false ) );  //TODO: add this also to panel-dependent as soon as filesystem
0526 support the display of search-results
0527 }
0528 */
0529 
0530 exp_Ask::exp_Ask()
0531 {
0532     _expression = "Ask";
0533     _description = i18n("Ask Parameter from User...");
0534     _needPanel = false;
0535 
0536     addParameter(exp_parameter(i18n("Question:"), "Where do you want do go today?", true));
0537     addParameter(exp_parameter(i18n("Preset (optional):"), "", false));
0538     addParameter(exp_parameter(i18n("Caption (optional):"), "", false));
0539 }
0540 TagString exp_Ask::expFunc(const KrPanel *, const QStringList &parameter, const bool &, Expander &exp) const
0541 {
0542     QString caption, preset, result;
0543 
0544     if (parameter.count() == 0) {
0545         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Ask.")));
0546         return QString();
0547     }
0548 
0549     if (parameter.count() <= 2 || parameter[2].isEmpty())
0550         caption = i18n("User Action");
0551     else
0552         caption = parameter[2];
0553     if (parameter.count() <= 1 || parameter[1].isEmpty())
0554         preset.clear();
0555     else
0556         preset = parameter[1];
0557 
0558     bool ok;
0559     result = QInputDialog::getText(krMainWindow, caption, parameter[0], QLineEdit::Normal, preset, &ok);
0560 
0561     if (ok)
0562         return result;
0563     else {
0564         // user cancelled
0565         setError(exp, Error(Error::exp_S_ERROR, Error::exp_C_USER));
0566         return QString();
0567     }
0568 }
0569 
0570 exp_Clipboard::exp_Clipboard()
0571 {
0572     _expression = "Clipboard";
0573     _description = i18n("Copy to Clipboard...");
0574     _needPanel = false;
0575 
0576     addParameter(exp_parameter(i18n("What to copy:"), "__placeholder", true));
0577     addParameter(exp_parameter(i18n("Append to current clipboard content with this separator (optional):"), "", false));
0578 }
0579 TagString exp_Clipboard::expFunc(const KrPanel *, const TagStringList &parameter, const bool &, Expander &exp) const
0580 {
0581     //    qDebug() << "Expander::exp_Clipboard, parameter[0]: '" << parameter[0] << "', Clipboard: " << QApplication::clipboard()->text();
0582     if (parameter.count() == 0) {
0583         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 1 parameter is required for Clipboard.")));
0584         return QString();
0585     }
0586 
0587     QStringList lst = splitEach(parameter[0]);
0588     if (parameter.count() > 1 && !parameter[1].isSimple()) {
0589         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: %Each% may not be in the second argument of %Clipboard%")));
0590         return QString();
0591     }
0592     if (parameter.count() <= 1 || parameter[1].string().isEmpty() || QApplication::clipboard()->text().isEmpty())
0593         QApplication::clipboard()->setText(lst.join("\n"));
0594     else
0595         QApplication::clipboard()->setText(QApplication::clipboard()->text() + parameter[1].string() + lst.join("\n"));
0596 
0597     return QString(); // this doesn't return anything, that's normal!
0598 }
0599 
0600 exp_Copy::exp_Copy()
0601 {
0602     _expression = "Copy";
0603     _description = i18n("Copy a File/Folder...");
0604     _needPanel = false;
0605 
0606     addParameter(exp_parameter(i18n("What to copy:"), "__placeholder", true));
0607     addParameter(exp_parameter(i18n("Where to copy:"), "__placeholder", true));
0608 }
0609 TagString exp_Copy::expFunc(const KrPanel *, const TagStringList &parameter, const bool &, Expander &exp) const
0610 {
0611     if (parameter.count() < 2) {
0612         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 2 parameter is required for Copy.")));
0613         return QString();
0614     }
0615 
0616     // basically the parameter can already be used as URL, but since QUrl has problems with ftp-proxy-urls (like ftp://username@proxyusername@url...) this is
0617     // necessary:
0618     const QStringList sourceList = splitEach(parameter[0]);
0619     QList<QUrl> sourceURLs;
0620     for (const QString &source : sourceList) {
0621         sourceURLs.append(QUrl::fromUserInput(source, QString(), QUrl::AssumeLocalFile));
0622     }
0623 
0624     if (!parameter[1].isSimple()) {
0625         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: %Each% may not be in the second argument of %Copy%")));
0626         return QString();
0627     }
0628 
0629     // or transform(...) ?
0630     const QUrl dest = QUrl::fromUserInput(parameter[1].string(), QString(), QUrl::AssumeLocalFile);
0631 
0632     if (!dest.isValid() || find_if(sourceURLs.constBegin(), sourceURLs.constEnd(), not1(mem_fun_ref(&QUrl::isValid))) != sourceURLs.constEnd()) {
0633         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: invalid URLs in %_Copy(\"src\", \"dest\")%")));
0634         return QString();
0635     }
0636 
0637     FileSystemProvider::instance().startCopyFiles(sourceURLs, dest);
0638 
0639     return QString(); // this doesn't return everything, that's normal!
0640 }
0641 
0642 exp_Move::exp_Move()
0643 {
0644     _expression = "Move";
0645     _description = i18n("Move/Rename a File/Folder...");
0646     _needPanel = false;
0647 
0648     addParameter(exp_parameter(i18n("What to move/rename:"), "__placeholder", true));
0649     addParameter(exp_parameter(i18n("New target/name:"), "__placeholder", true));
0650 }
0651 TagString exp_Move::expFunc(const KrPanel *, const TagStringList &parameter, const bool &, Expander &exp) const
0652 {
0653     if (parameter.count() < 2) {
0654         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: at least 2 parameter is required for Move.")));
0655         return QString();
0656     }
0657 
0658     // basically the parameter can already be used as URL, but since QUrl has problems with ftp-proxy-urls (like ftp://username@proxyusername@url...) this is
0659     // necessary:
0660     QStringList lst = splitEach(parameter[0]);
0661     if (!parameter[1].isSimple()) {
0662         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("%Each% may not be in the second argument of %Move%")));
0663         return QString();
0664     }
0665     QList<QUrl> src;
0666     for (QStringList::const_iterator it = lst.constBegin(), end = lst.constEnd(); it != end; ++it)
0667         src.push_back(QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile));
0668     // or transform(...) ?
0669     QUrl dest = QUrl::fromUserInput(parameter[1].string(), QString(), QUrl::AssumeLocalFile);
0670 
0671     if (!dest.isValid() || find_if(src.constBegin(), src.constEnd(), not1(mem_fun_ref(&QUrl::isValid))) != src.constEnd()) {
0672         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: invalid URLs in %_Move(\"src\", \"dest\")%")));
0673         return QString();
0674     }
0675 
0676     FileSystemProvider::instance().startCopyFiles(src, dest, KIO::CopyJob::Move);
0677 
0678     return QString(); // this doesn't return anything, that's normal!
0679 }
0680 
0681 #ifdef SYNCHRONIZER_ENABLED
0682 exp_Sync::exp_Sync()
0683 {
0684     _expression = "Sync";
0685     _description = i18n("Load a Synchronizer Profile...");
0686     _needPanel = false;
0687 
0688     addParameter(exp_parameter(i18n("Choose a profile:"), "__syncprofile", true));
0689 }
0690 TagString exp_Sync::expFunc(const KrPanel *, const QStringList &parameter, const bool &, Expander &exp) const
0691 {
0692     if (parameter.count() == 0 || parameter[0].isEmpty()) {
0693         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_Sync(profile)%")));
0694         return QString();
0695     }
0696 
0697     SynchronizerGUI *synchronizerDialog = new SynchronizerGUI(MAIN_VIEW, parameter[0]);
0698     synchronizerDialog->show(); // destroyed on close
0699 
0700     return QString(); // this doesn't return everything, that's normal!
0701 }
0702 #endif
0703 
0704 exp_NewSearch::exp_NewSearch()
0705 {
0706     _expression = "NewSearch";
0707     _description = i18n("Load a Searchmodule Profile...");
0708     _needPanel = false;
0709 
0710     addParameter(exp_parameter(i18n("Choose a profile:"), "__searchprofile", true));
0711 }
0712 TagString exp_NewSearch::expFunc(const KrPanel *, const QStringList &parameter, const bool &, Expander &exp) const
0713 {
0714     if (parameter.count() == 0 || parameter[0].isEmpty()) {
0715         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_NewSearch(profile)%")));
0716         return QString();
0717     }
0718 
0719     new KrSearchDialog(parameter[0], krApp);
0720 
0721     return QString(); // this doesn't return everything, that's normal!
0722 }
0723 
0724 exp_Profile::exp_Profile()
0725 {
0726     _expression = "Profile";
0727     _description = i18n("Load a Panel Profile...");
0728     _needPanel = false;
0729 
0730     addParameter(exp_parameter(i18n("Choose a profile:"), "__panelprofile", true));
0731 }
0732 TagString exp_Profile::expFunc(const KrPanel *, const QStringList &parameter, const bool &, Expander &exp) const
0733 {
0734     if (parameter.count() == 0 || parameter[0].isEmpty()) {
0735         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no profile specified for %_Profile(profile)%; abort...")));
0736         return QString();
0737     }
0738 
0739     MAIN_VIEW->profiles(parameter[0]);
0740 
0741     return QString(); // this doesn't return everything, that's normal!
0742 }
0743 
0744 exp_Each::exp_Each()
0745 {
0746     _expression = "Each";
0747     _description = i18n("Separate Program Call for Each...");
0748     _needPanel = true;
0749 
0750     addParameter(exp_parameter(i18n("Which items:"), "__choose:All;Files;Dirs;Selected", false));
0751     addParameter(exp_parameter(i18n("Omit the current path (optional)"), "__no", false));
0752     addParameter(exp_parameter(i18n("Mask (optional, all but 'Selected'):"), "__select", false));
0753     addParameter(exp_parameter(i18n("Automatically escape spaces"), "__yes", false));
0754 }
0755 TagString exp_Each::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &useUrl, Expander &exp) const
0756 {
0757     NEED_PANEL
0758 
0759     QString mask;
0760     if (parameter.count() <= 2 || parameter[2].isEmpty())
0761         mask = '*';
0762     else
0763         mask = parameter[2];
0764 
0765     TagString ret;
0766     QStringList l = fileList(panel,
0767                              parameter.empty() ? QString() : parameter[0].toLower(),
0768                              mask,
0769                              parameter.count() > 1 && parameter[1].toLower() == "yes",
0770                              useUrl,
0771                              exp,
0772                              "Each");
0773 
0774     if (!(parameter.count() <= 3 || parameter[3].toLower() != "yes"))
0775         transform(l.begin(), l.end(), l.begin(), bashquote);
0776 
0777     ret.insertTag(0, l);
0778     return ret;
0779 }
0780 
0781 exp_ColSort::exp_ColSort()
0782 {
0783     _expression = "ColSort";
0784     _description = i18n("Set Sorting for This Panel...");
0785     _needPanel = true;
0786 
0787     addParameter(exp_parameter(i18n("Choose a column:"), "__choose:Name;Ext;Type;Size;Modified;Perms;rwx;Owner;Group", true));
0788     addParameter(exp_parameter(i18n("Choose a sort sequence:"), "__choose:Toggle;Asc;Desc", false));
0789 }
0790 TagString exp_ColSort::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &, Expander &exp) const
0791 {
0792     NEED_PANEL
0793 
0794     if (parameter.count() == 0 || parameter[0].isEmpty()) {
0795         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no column specified for %_ColSort(column)%")));
0796         return QString();
0797     }
0798 
0799     KrViewProperties::ColumnType oldColumn = panel->view->properties()->sortColumn;
0800     KrViewProperties::ColumnType column = oldColumn;
0801 
0802     if (parameter[0].toLower() == "name") {
0803         column = KrViewProperties::Name;
0804     } else if (parameter[0].toLower() == "ext") {
0805         column = KrViewProperties::Ext;
0806     } else if (parameter[0].toLower() == "type") {
0807         column = KrViewProperties::Type;
0808     } else if (parameter[0].toLower() == "size") {
0809         column = KrViewProperties::Size;
0810     } else if (parameter[0].toLower() == "modified") {
0811         column = KrViewProperties::Modified;
0812     } else if (parameter[0].toLower() == "changed") {
0813         column = KrViewProperties::Changed;
0814     } else if (parameter[0].toLower() == "accessed") {
0815         column = KrViewProperties::Accessed;
0816     } else if (parameter[0].toLower() == "perms") {
0817         column = KrViewProperties::Permissions;
0818     } else if (parameter[0].toLower() == "rwx") {
0819         column = KrViewProperties::KrPermissions;
0820     } else if (parameter[0].toLower() == "owner") {
0821         column = KrViewProperties::Owner;
0822     } else if (parameter[0].toLower() == "group") {
0823         column = KrViewProperties::Group;
0824     } else {
0825         setError(exp, Error(Error::exp_S_WARNING, Error::exp_C_ARGUMENT, i18n("Expander: unknown column specified for %_ColSort(%1)%", parameter[0])));
0826         return QString();
0827     }
0828 
0829     bool descending = panel->view->properties()->sortOptions & KrViewProperties::Descending;
0830 
0831     if (parameter.count() <= 1 || (parameter[1].toLower() != "asc" && parameter[1].toLower() != "desc")) { // no sortdir parameter
0832         if (column == oldColumn) // reverse direction if column is unchanged
0833             descending = !descending;
0834         else // otherwise set to ascending
0835             descending = false;
0836     } else { // sortdir specified
0837         if (parameter[1].toLower() == "asc")
0838             descending = false;
0839         else // == desc
0840             descending = true;
0841     }
0842 
0843     panel->view->setSortMode(column, descending);
0844 
0845     return QString(); // this doesn't return anything, that's normal!
0846 }
0847 
0848 exp_PanelSize::exp_PanelSize()
0849 {
0850     _expression = "PanelSize";
0851     _description = i18n("Set Relation Between the Panels...");
0852     _needPanel = true;
0853 
0854     addParameter(exp_parameter(i18n("Set the new size in percent:"), "__int:0;100;5;50", true));
0855 }
0856 TagString exp_PanelSize::expFunc(const KrPanel *panel, const QStringList &parameter, const bool &, Expander &exp) const
0857 {
0858     NEED_PANEL
0859     int newSize;
0860 
0861     if (parameter.count() == 0 || parameter[0].isEmpty())
0862         newSize = 50; // default is 50%
0863     else
0864         newSize = parameter[0].toInt();
0865 
0866     if (newSize < 0 || newSize > 100) {
0867         setError(exp,
0868                  Error(Error::exp_S_FATAL,
0869                        Error::exp_C_ARGUMENT,
0870                        i18n("Expander: Value %1 out of range for %_PanelSize(percent)%. The first parameter has to be >0 and <100", newSize)));
0871         return QString();
0872     }
0873 
0874     MAIN_VIEW->setPanelSize(panel->isLeft(), newSize);
0875 
0876     return QString(); // this doesn't return everything, that's normal!
0877 }
0878 
0879 exp_View::exp_View()
0880 {
0881     _expression = "View";
0882     _description = i18n("View File with Krusader's Internal Viewer...");
0883     _needPanel = false;
0884 
0885     addParameter(exp_parameter(i18n("Which file to view (normally '%aCurrent%'):"), "__placeholder", true));
0886     addParameter(exp_parameter(i18n("Choose a view mode:"), "__choose:generic;text;hex", false));
0887     // addParameter( exp_parameter( i18n("Choose a window-mode"), "__choose:tab;window;panel", false ) );
0888     // TODO: window-mode 'panel' should open the file in the third-hand viewer
0889     addParameter(exp_parameter(i18n("Choose a window mode:"), "__choose:tab;window", false));
0890 }
0891 TagString exp_View::expFunc(const KrPanel *, const QStringList &parameter, const bool &, Expander &exp) const
0892 {
0893     if (parameter.count() == 0 || parameter[0].isEmpty()) {
0894         setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_ARGUMENT, i18n("Expander: no file to view in %_View(filename)%")));
0895         return QString();
0896     }
0897 
0898     QString viewMode, windowMode;
0899     if (parameter.count() <= 1 || parameter[1].isEmpty())
0900         viewMode = "generic";
0901     else
0902         viewMode = parameter[1];
0903 
0904     if (parameter.count() <= 2 || parameter[2].isEmpty())
0905         windowMode = "tab";
0906     else
0907         windowMode = parameter[2];
0908 
0909     KrViewer::Mode mode = KrViewer::Generic;
0910     if (viewMode == "text")
0911         mode = KrViewer::Text;
0912     else if (viewMode == "hex")
0913         mode = KrViewer::Hex;
0914 
0915     QUrl url = QUrl::fromUserInput(parameter[0], QString(), QUrl::AssumeLocalFile);
0916     KrViewer::view(url, mode, (windowMode == "window"));
0917     // TODO: Call the viewer with viewMode and windowMode. Filename is in parameter[0].
0918     //  It would be nice if parameter[0] could also be a space-separated filename-list (provided if the first parameter is %aList(selected)%)
0919 
0920     return QString(); // this doesn't return everything, that's normal!
0921 }
0922 
0923 /////////////////////////////////////////////////////////////////////////////////////////////////
0924 ////////////////////////////// end of expander classes ////////////////////////////////
0925 /////////////////////////////////////////////////////////////////////////////////////////////////
0926 
0927 TagString exp_simpleplaceholder::expFunc(const KrPanel *p, const TagStringList &parameter, const bool &useUrl, Expander &exp) const
0928 {
0929     QStringList lst;
0930     for (const auto &it : parameter)
0931         if (it.isSimple())
0932             lst.push_back(it.string());
0933         else {
0934             setError(exp, Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("%Each% is not allowed in parameter to %1", description())));
0935             return QString();
0936         }
0937     return expFunc(p, lst, useUrl, exp);
0938 }
0939 
0940 }
0941 
0942 KrPanel *Expander::getPanel(const char panelIndicator, const exp_placeholder *pl, Expander &exp)
0943 {
0944     switch (panelIndicator) {
0945     case 'a':
0946         return ACTIVE_PANEL;
0947     case 'o':
0948         return OTHER_PANEL;
0949     case 'l':
0950         return LEFT_PANEL;
0951     case 'r':
0952         return RIGHT_PANEL;
0953     case '_':
0954         return nullptr;
0955     default:
0956         exp.setError(
0957             Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Expander: Bad panel specifier %1 in placeholder %2", panelIndicator, pl->description())));
0958         return nullptr;
0959     }
0960 }
0961 
0962 void Expander::expand(const QString &stringToExpand, bool useUrl)
0963 {
0964     TagString result = expandCurrent(stringToExpand, useUrl);
0965     if (error())
0966         return;
0967 
0968     if (!result.isSimple())
0969         resultList = splitEach(result);
0970     else
0971         resultList.append(result.string());
0972 
0973     //    qWarning() << resultList[0];
0974 }
0975 
0976 TagString Expander::expandCurrent(const QString &stringToExpand, bool useUrl)
0977 {
0978     TagString result;
0979     QString exp;
0980     TagString tmpResult;
0981     int begin, end, i;
0982     //    int brackets = 0;
0983     //    bool inQuotes = false;
0984     int idx = 0;
0985     while (idx < stringToExpand.length()) {
0986         if ((begin = stringToExpand.indexOf('%', idx)) == -1)
0987             break;
0988         if ((end = findEnd(stringToExpand, begin)) == -1) {
0989             // xgettext:no-c-format
0990             setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: unterminated % in Expander")));
0991             return QString();
0992         }
0993 
0994         result += stringToExpand.mid(idx, begin - idx); // copy until the start of %exp%
0995 
0996         // get the expression, and expand it using the correct expander function
0997         exp = stringToExpand.mid(begin + 1, end - begin - 1);
0998         //       qDebug() << "------------- exp: '" << exp << "'";
0999         if (exp.isEmpty())
1000             result += QString(QChar('%'));
1001         else {
1002             TagStringList parameter = separateParameter(&exp, useUrl);
1003             if (error())
1004                 return QString();
1005             char panelIndicator = exp.toLower()[0].toLatin1();
1006             exp.replace(0, 1, "");
1007             for (i = 0; i < placeholderCount(); ++i)
1008                 if (exp == placeholder(i)->expression()) {
1009                     //               qDebug() << "---------------------------------------";
1010                     tmpResult = placeholder(i)->expFunc(getPanel(panelIndicator, placeholder(i), *this), parameter, useUrl, *this);
1011                     if (error()) {
1012                         return QString();
1013                     } else
1014                         result += tmpResult;
1015                     //               qDebug() << "---------------------------------------";
1016                     break;
1017                 }
1018             if (i == placeholderCount()) { // didn't find an expander
1019                 setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: unrecognized %%%1%2%% in Expander", panelIndicator, exp)));
1020                 return QString();
1021             }
1022         } // else
1023         idx = end + 1;
1024     }
1025     // copy the rest of the string
1026     result += stringToExpand.mid(idx);
1027     //    qDebug() << "============== result '" << result << "'";
1028     return result;
1029 }
1030 
1031 QStringList Expander::splitEach(TagString stringToSplit)
1032 {
1033     if (stringToSplit.isSimple()) {
1034         //   qWarning() << stringToSplit.string();
1035         QStringList l;
1036         l << stringToSplit.string();
1037         return l;
1038     }
1039     pair<uint, QStringList> pl = *stringToSplit.tagsBegin();
1040     stringToSplit.eraseTag(stringToSplit.tagsBegin());
1041     QStringList ret;
1042     for (QStringList::const_iterator it = pl.second.constBegin(), end = pl.second.constEnd(); it != end; ++it) {
1043         TagString s = stringToSplit;
1044         s.insert(pl.first, *it);
1045         ret += splitEach(s);
1046     }
1047     return ret;
1048     //    qDebug() << "stringToSplit: " << stringToSplit;
1049 }
1050 
1051 TagStringList Expander::separateParameter(QString *const exp, bool useUrl)
1052 {
1053     TagStringList parameter;
1054     QStringList parameter1;
1055     QString result;
1056     int begin, end;
1057     if ((begin = exp->indexOf('(')) != -1) {
1058         if ((end = exp->lastIndexOf(')')) == -1) {
1059             setError(Error(Error::exp_S_FATAL, Error::exp_C_SYNTAX, i18n("Error: missing ')' in Expander")));
1060             return TagStringList();
1061         }
1062         result = exp->mid(begin + 1, end - begin - 1);
1063         *exp = exp->left(begin);
1064 
1065         bool inQuotes = false;
1066         int idx = 0;
1067         begin = 0;
1068         while (idx < result.length()) {
1069             if (result[idx].toLatin1() == '\\') {
1070                 if (result[idx + 1].toLatin1() == '"')
1071                     result.replace(idx, 1, "");
1072             }
1073             if (result[idx].toLatin1() == '"')
1074                 inQuotes = !inQuotes;
1075             if (result[idx].toLatin1() == ',' && !inQuotes) {
1076                 parameter1.append(result.mid(begin, idx - begin));
1077                 begin = idx + 1;
1078                 //             qWarning() << " ---- parameter: " << parameter.join(";");
1079             }
1080             idx++;
1081         }
1082         parameter1.append(result.mid(begin, idx - begin)); // don't forget the last one
1083 
1084         for (auto &it : parameter1) {
1085             it = it.trimmed();
1086             if (it.left(1) == "\"")
1087                 it = it.mid(1, it.length() - 2);
1088             parameter.push_back(expandCurrent(it, useUrl));
1089             if (error())
1090                 return TagStringList();
1091         }
1092     }
1093 
1094     //    qWarning() << "------- exp: " << *exp << " ---- parameter: " << parameter.join(";");
1095     return parameter;
1096 }
1097 
1098 int Expander::findEnd(const QString &str, int start)
1099 {
1100     int end = str.indexOf('%', start + 1);
1101     if (end == -1)
1102         return end;
1103     int bracket = str.indexOf('(', start + 1);
1104     if (end < bracket || bracket == -1)
1105         return end;
1106 
1107     int idx = bracket + 1;
1108     bool inQuotes = false;
1109     int depth = 1;
1110     while (idx < str.length()) {
1111         switch (str[idx].toLatin1()) {
1112         case '\\':
1113             idx++;
1114             break;
1115         case '"':
1116             inQuotes = !inQuotes;
1117             break;
1118         case '(':
1119             if (!inQuotes)
1120                 depth++;
1121             break;
1122         case ')':
1123             if (!inQuotes)
1124                 --depth;
1125             break;
1126         case '%':
1127             if (depth == 0)
1128                 return idx;
1129         } // switch
1130         idx++;
1131     } // while
1132     // failsafe
1133     return -1;
1134 }
1135 
1136 QList<const exp_placeholder *> &Expander::_placeholder()
1137 {
1138     static QList<const exp_placeholder *> ret;
1139     if (!ret.count()) {
1140         ret << new exp_View;
1141         ret << new exp_PanelSize;
1142         ret << new exp_ColSort;
1143         ret << new exp_Each;
1144         ret << new exp_Profile;
1145         ret << new exp_NewSearch;
1146 #ifdef SYNCHRONIZER_ENABLED
1147         ret << new exp_Sync;
1148 #endif
1149         ret << new exp_Move;
1150         ret << new exp_Copy;
1151         ret << new exp_Goto;
1152         ret << new exp_Select;
1153         ret << new exp_Clipboard;
1154         ret << new exp_Ask;
1155         ret << new exp_ListFile;
1156         ret << new exp_List;
1157         ret << new exp_Current;
1158         ret << new exp_Filter;
1159         ret << new exp_Count;
1160         ret << new exp_Path;
1161     }
1162     return ret;
1163 }