File indexing completed on 2024-04-28 07:46:48

0001 /*
0002     SPDX-FileCopyrightText: 2019 Dominik Haumann <dhaumann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "katevariableexpansionmanager.h"
0008 #include "katevariableexpansionhelpers.h"
0009 
0010 #include "katedocument.h"
0011 #include "kateglobal.h"
0012 
0013 #include <KLocalizedString>
0014 
0015 #include <QAbstractItemModel>
0016 #include <QListView>
0017 #include <QVBoxLayout>
0018 
0019 #include <QDate>
0020 #include <QDir>
0021 #include <QFileInfo>
0022 #include <QJSEngine>
0023 #include <QLocale>
0024 #include <QTime>
0025 #include <QUuid>
0026 
0027 static void registerVariables(KateVariableExpansionManager &mng)
0028 {
0029     using KTextEditor::Variable;
0030 
0031     mng.addVariable(Variable(
0032         QStringLiteral("Document:FileBaseName"),
0033         i18n("File base name without path and suffix of the current document."),
0034         [](const QStringView &, KTextEditor::View *view) {
0035             const auto url = view ? view->document()->url().toLocalFile() : QString();
0036             return QFileInfo(url).baseName();
0037         },
0038         false));
0039     mng.addVariable(Variable(
0040         QStringLiteral("Document:FileExtension"),
0041         i18n("File extension of the current document."),
0042         [](const QStringView &, KTextEditor::View *view) {
0043             const auto url = view ? view->document()->url().toLocalFile() : QString();
0044             return QFileInfo(url).completeSuffix();
0045         },
0046         false));
0047     mng.addVariable(Variable(
0048         QStringLiteral("Document:FileName"),
0049         i18n("File name without path of the current document."),
0050         [](const QStringView &, KTextEditor::View *view) {
0051             const auto url = view ? view->document()->url().toLocalFile() : QString();
0052             return QFileInfo(url).fileName();
0053         },
0054         false));
0055     mng.addVariable(Variable(
0056         QStringLiteral("Document:FilePath"),
0057         i18n("Full path of the current document including the file name."),
0058         [](const QStringView &, KTextEditor::View *view) {
0059             const auto url = view ? view->document()->url().toLocalFile() : QString();
0060             return QFileInfo(url).absoluteFilePath();
0061         },
0062         false));
0063     mng.addVariable(Variable(
0064         QStringLiteral("Document:Text"),
0065         i18n("Contents of the current document."),
0066         [](const QStringView &, KTextEditor::View *view) {
0067             return view ? view->document()->text() : QString();
0068         },
0069         false));
0070     mng.addVariable(Variable(
0071         QStringLiteral("Document:Path"),
0072         i18n("Full path of the current document excluding the file name."),
0073         [](const QStringView &, KTextEditor::View *view) {
0074             const auto url = view ? view->document()->url().toLocalFile() : QString();
0075             return QFileInfo(url).absolutePath();
0076         },
0077         false));
0078     mng.addVariable(Variable(
0079         QStringLiteral("Document:NativeFilePath"),
0080         i18n("Full document path including file name, with native path separator (backslash on Windows)."),
0081         [](const QStringView &, KTextEditor::View *view) {
0082             const auto url = view ? view->document()->url().toLocalFile() : QString();
0083             return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absoluteFilePath());
0084         },
0085         false));
0086     mng.addVariable(Variable(
0087         QStringLiteral("Document:NativePath"),
0088         i18n("Full document path excluding file name, with native path separator (backslash on Windows)."),
0089         [](const QStringView &, KTextEditor::View *view) {
0090             const auto url = view ? view->document()->url().toLocalFile() : QString();
0091             return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absolutePath());
0092         },
0093         false));
0094     mng.addVariable(Variable(
0095         QStringLiteral("Document:Cursor:Line"),
0096         i18n("Line number of the text cursor position in current document (starts with 0)."),
0097         [](const QStringView &, KTextEditor::View *view) {
0098             return view ? QString::number(view->cursorPosition().line()) : QString();
0099         },
0100         false));
0101     mng.addVariable(Variable(
0102         QStringLiteral("Document:Cursor:Column"),
0103         i18n("Column number of the text cursor position in current document (starts with 0)."),
0104         [](const QStringView &, KTextEditor::View *view) {
0105             return view ? QString::number(view->cursorPosition().column()) : QString();
0106         },
0107         false));
0108     mng.addVariable(Variable(
0109         QStringLiteral("Document:Cursor:XPos"),
0110         i18n("X component in global screen coordinates of the cursor position."),
0111         [](const QStringView &, KTextEditor::View *view) {
0112             return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).x()) : QString();
0113         },
0114         false));
0115     mng.addVariable(Variable(
0116         QStringLiteral("Document:Cursor:YPos"),
0117         i18n("Y component in global screen coordinates of the cursor position."),
0118         [](const QStringView &, KTextEditor::View *view) {
0119             return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).y()) : QString();
0120         },
0121         false));
0122     mng.addVariable(Variable(
0123         QStringLiteral("Document:Selection:Text"),
0124         i18n("Text selection of the current document."),
0125         [](const QStringView &, KTextEditor::View *view) {
0126             return (view && view->selection()) ? view->selectionText() : QString();
0127         },
0128         false));
0129     mng.addVariable(Variable(
0130         QStringLiteral("Document:Selection:StartLine"),
0131         i18n("Start line of selected text of the current document."),
0132         [](const QStringView &, KTextEditor::View *view) {
0133             return (view && view->selection()) ? QString::number(view->selectionRange().start().line()) : QString();
0134         },
0135         false));
0136     mng.addVariable(Variable(
0137         QStringLiteral("Document:Selection:StartColumn"),
0138         i18n("Start column of selected text of the current document."),
0139         [](const QStringView &, KTextEditor::View *view) {
0140             return (view && view->selection()) ? QString::number(view->selectionRange().start().column()) : QString();
0141         },
0142         false));
0143     mng.addVariable(Variable(
0144         QStringLiteral("Document:Selection:EndLine"),
0145         i18n("End line of selected text of the current document."),
0146         [](const QStringView &, KTextEditor::View *view) {
0147             return (view && view->selection()) ? QString::number(view->selectionRange().end().line()) : QString();
0148         },
0149         false));
0150     mng.addVariable(Variable(
0151         QStringLiteral("Document:Selection:EndColumn"),
0152         i18n("End column of selected text of the current document."),
0153         [](const QStringView &, KTextEditor::View *view) {
0154             return (view && view->selection()) ? QString::number(view->selectionRange().end().column()) : QString();
0155         },
0156         false));
0157     mng.addVariable(Variable(
0158         QStringLiteral("Document:RowCount"),
0159         i18n("Number of rows of the current document."),
0160         [](const QStringView &, KTextEditor::View *view) {
0161             return view ? QString::number(view->document()->lines()) : QString();
0162         },
0163         false));
0164     mng.addVariable(Variable(
0165         QStringLiteral("Document:Variable:"),
0166         i18n("Read a document variable."),
0167         [](const QStringView &str, KTextEditor::View *view) {
0168             return view ? qobject_cast<KTextEditor::DocumentPrivate *>(view->document())->variable(str.mid(18).toString()) : QString();
0169         },
0170         true));
0171 
0172     mng.addVariable(Variable(
0173         QStringLiteral("Date:Locale"),
0174         i18n("The current date in current locale format."),
0175         [](const QStringView &, KTextEditor::View *) {
0176             return QLocale().toString(QDate::currentDate(), QLocale::ShortFormat);
0177         },
0178         false));
0179     mng.addVariable(Variable(
0180         QStringLiteral("Date:ISO"),
0181         i18n("The current date (ISO)."),
0182         [](const QStringView &, KTextEditor::View *) {
0183             return QDate::currentDate().toString(Qt::ISODate);
0184         },
0185         false));
0186     mng.addVariable(Variable(
0187         QStringLiteral("Date:"),
0188         i18n("The current date (QDate formatstring)."),
0189         [](const QStringView &str, KTextEditor::View *) {
0190             return QDate::currentDate().toString(str.mid(5));
0191         },
0192         true));
0193 
0194     mng.addVariable(Variable(
0195         QStringLiteral("Time:Locale"),
0196         i18n("The current time in current locale format."),
0197         [](const QStringView &, KTextEditor::View *) {
0198             return QLocale().toString(QTime::currentTime(), QLocale::ShortFormat);
0199         },
0200         false));
0201     mng.addVariable(Variable(
0202         QStringLiteral("Time:ISO"),
0203         i18n("The current time (ISO)."),
0204         [](const QStringView &, KTextEditor::View *) {
0205             return QTime::currentTime().toString(Qt::ISODate);
0206         },
0207         false));
0208     mng.addVariable(Variable(
0209         QStringLiteral("Time:"),
0210         i18n("The current time (QTime formatstring)."),
0211         [](const QStringView &str, KTextEditor::View *) {
0212             return QTime::currentTime().toString(str.mid(5));
0213         },
0214         true));
0215 
0216     mng.addVariable(Variable(
0217         QStringLiteral("ENV:"),
0218         i18n("Access to environment variables."),
0219         [](const QStringView &str, KTextEditor::View *) {
0220             return QString::fromLocal8Bit(qgetenv(str.mid(4).toLocal8Bit().constData()));
0221         },
0222         true));
0223 
0224     mng.addVariable(Variable(
0225         QStringLiteral("JS:"),
0226         i18n("Evaluate simple JavaScript statements."),
0227         [](const QStringView &str, KTextEditor::View *) {
0228             QJSEngine jsEngine;
0229             const QJSValue out = jsEngine.evaluate(str.toString());
0230             return out.toString();
0231         },
0232         true));
0233 
0234     mng.addVariable(Variable(
0235         QStringLiteral("PercentEncoded:"),
0236         i18n("Encode text to make it URL compatible."),
0237         [](const QStringView &str, KTextEditor::View *) {
0238             return QString::fromUtf8(QUrl::toPercentEncoding(str.mid(15).toString()));
0239         },
0240         true));
0241 
0242     mng.addVariable(Variable(
0243         QStringLiteral("UUID"),
0244         i18n("Generate a new UUID."),
0245         [](const QStringView &, KTextEditor::View *) {
0246             return QUuid::createUuid().toString(QUuid::WithoutBraces);
0247         },
0248         false));
0249 }
0250 
0251 KateVariableExpansionManager::KateVariableExpansionManager(QObject *parent)
0252     : QObject(parent)
0253 {
0254     // register default variables for expansion
0255     registerVariables(*this);
0256 }
0257 
0258 bool KateVariableExpansionManager::addVariable(const KTextEditor::Variable &var)
0259 {
0260     if (!var.isValid()) {
0261         return false;
0262     }
0263 
0264     // reject duplicates
0265     const auto alreadyExists = std::any_of(m_variables.begin(), m_variables.end(), [&var](const KTextEditor::Variable &v) {
0266         return var.name() == v.name();
0267     });
0268     if (alreadyExists) {
0269         return false;
0270     }
0271 
0272     // require a ':' in prefix matches (aka %{JS:1+1})
0273     if (var.isPrefixMatch() && !var.name().contains(QLatin1Char(':'))) {
0274         return false;
0275     }
0276 
0277     m_variables.push_back(var);
0278     return true;
0279 }
0280 
0281 bool KateVariableExpansionManager::removeVariable(const QString &name)
0282 {
0283     auto it = std::find_if(m_variables.begin(), m_variables.end(), [&name](const KTextEditor::Variable &var) {
0284         return var.name() == name;
0285     });
0286     if (it != m_variables.end()) {
0287         m_variables.erase(it);
0288         return true;
0289     }
0290     return false;
0291 }
0292 
0293 KTextEditor::Variable KateVariableExpansionManager::variable(const QString &name) const
0294 {
0295     auto it = std::find_if(m_variables.begin(), m_variables.end(), [&name](const KTextEditor::Variable &var) {
0296         return var.name() == name;
0297     });
0298     if (it != m_variables.end()) {
0299         return *it;
0300     }
0301     return {};
0302 }
0303 
0304 const QList<KTextEditor::Variable> &KateVariableExpansionManager::variables() const
0305 {
0306     return m_variables;
0307 }
0308 
0309 bool KateVariableExpansionManager::expandVariable(const QString &name, KTextEditor::View *view, QString &output) const
0310 {
0311     // first try exact matches
0312     auto var = variable(name);
0313     if (!var.isValid()) {
0314         // try prefix matching
0315         for (auto &v : m_variables) {
0316             if (v.isPrefixMatch() && name.startsWith(v.name())) {
0317                 var = v;
0318                 break;
0319             }
0320         }
0321     }
0322 
0323     if (var.isValid()) {
0324         output = var.evaluate(name, view);
0325         return true;
0326     }
0327 
0328     return false;
0329 }
0330 
0331 QString KateVariableExpansionManager::expandText(const QString &text, KTextEditor::View *view)
0332 {
0333     return KateMacroExpander::expandMacro(text, view);
0334 }
0335 
0336 void KateVariableExpansionManager::showDialog(const QList<QWidget *> &widgets, const QStringList &names) const
0337 {
0338     // avoid any work in case no widgets or only nullptrs were provided
0339     if (widgets.isEmpty() || std::all_of(widgets.cbegin(), widgets.cend(), [](const QWidget *w) {
0340             return w == nullptr;
0341         })) {
0342         return;
0343     }
0344 
0345     // collect variables
0346     QList<KTextEditor::Variable> vars;
0347     if (!names.isEmpty()) {
0348         for (const auto &name : names) {
0349             const auto var = variable(name);
0350             if (var.isValid()) {
0351                 vars.push_back(var);
0352             }
0353             // else: Not found, silently ignore for now
0354             //       Maybe raise a qCWarning(LOG_KTE)?
0355         }
0356     } else {
0357         vars = variables();
0358     }
0359 
0360     // if we have no vars at all, do nothing
0361     if (vars.isEmpty()) {
0362         return;
0363     }
0364 
0365     // find parent dialog (for taskbar sharing, centering, ...)
0366     QWidget *parentDialog = nullptr;
0367     for (auto widget : widgets) {
0368         if (widget) {
0369             parentDialog = widget->window();
0370             break;
0371         }
0372     }
0373 
0374     // show dialog
0375     auto dlg = new KateVariableExpansionDialog(parentDialog);
0376     for (auto widget : widgets) {
0377         if (widget) {
0378             dlg->addWidget(widget);
0379         }
0380     }
0381 
0382     // add provided variables...
0383     for (const auto &var : std::as_const(vars)) {
0384         if (var.isValid()) {
0385             dlg->addVariable(var);
0386         }
0387     }
0388 }
0389 
0390 // kate: space-indent on; indent-width 4; replace-tabs on;