File indexing completed on 2024-03-24 04:00:39
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;