1/*
2 SPDX-FileCopyrightText: 2019 Dominik Haumann <dhaumann@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "katevariableexpansionmanager.h"
8#include "katevariableexpansionhelpers.h"
9
10#include "katedocument.h"
11#include "kateglobal.h"
12
13#include <KLocalizedString>
14
15#include <QAbstractItemModel>
16#include <QListView>
17#include <QVBoxLayout>
18
19#include <QDate>
20#include <QDir>
21#include <QFileInfo>
22#include <QJSEngine>
23#include <QLocale>
24#include <QTime>
25#include <QUuid>
26
27static void registerVariables(KateVariableExpansionManager &mng)
28{
29 using KTextEditor::Variable;
30
31 mng.addVariable(variable: Variable(
32 QStringLiteral("Document:FileBaseName"),
33 i18n("File base name without path and suffix of the current document."),
34 [](const QStringView &, KTextEditor::View *view) {
35 const auto url = view ? view->document()->url().toLocalFile() : QString();
36 return QFileInfo(url).baseName();
37 },
38 false));
39 mng.addVariable(variable: Variable(
40 QStringLiteral("Document:FileExtension"),
41 i18n("File extension of the current document."),
42 [](const QStringView &, KTextEditor::View *view) {
43 const auto url = view ? view->document()->url().toLocalFile() : QString();
44 return QFileInfo(url).completeSuffix();
45 },
46 false));
47 mng.addVariable(variable: Variable(
48 QStringLiteral("Document:FileName"),
49 i18n("File name without path of the current document."),
50 [](const QStringView &, KTextEditor::View *view) {
51 const auto url = view ? view->document()->url().toLocalFile() : QString();
52 return QFileInfo(url).fileName();
53 },
54 false));
55 mng.addVariable(variable: Variable(
56 QStringLiteral("Document:FilePath"),
57 i18n("Full path of the current document including the file name."),
58 [](const QStringView &, KTextEditor::View *view) {
59 const auto url = view ? view->document()->url().toLocalFile() : QString();
60 return QFileInfo(url).absoluteFilePath();
61 },
62 false));
63 mng.addVariable(variable: Variable(
64 QStringLiteral("Document:Text"),
65 i18n("Contents of the current document."),
66 [](const QStringView &, KTextEditor::View *view) {
67 return view ? view->document()->text() : QString();
68 },
69 false));
70 mng.addVariable(variable: Variable(
71 QStringLiteral("Document:Path"),
72 i18n("Full path of the current document excluding the file name."),
73 [](const QStringView &, KTextEditor::View *view) {
74 const auto url = view ? view->document()->url().toLocalFile() : QString();
75 return QFileInfo(url).absolutePath();
76 },
77 false));
78 mng.addVariable(variable: Variable(
79 QStringLiteral("Document:NativeFilePath"),
80 i18n("Full document path including file name, with native path separator (backslash on Windows)."),
81 [](const QStringView &, KTextEditor::View *view) {
82 const auto url = view ? view->document()->url().toLocalFile() : QString();
83 return url.isEmpty() ? QString() : QDir::toNativeSeparators(pathName: QFileInfo(url).absoluteFilePath());
84 },
85 false));
86 mng.addVariable(variable: Variable(
87 QStringLiteral("Document:NativePath"),
88 i18n("Full document path excluding file name, with native path separator (backslash on Windows)."),
89 [](const QStringView &, KTextEditor::View *view) {
90 const auto url = view ? view->document()->url().toLocalFile() : QString();
91 return url.isEmpty() ? QString() : QDir::toNativeSeparators(pathName: QFileInfo(url).absolutePath());
92 },
93 false));
94 mng.addVariable(variable: Variable(
95 QStringLiteral("Document:Cursor:Line"),
96 i18n("Line number of the text cursor position in current document (starts with 0)."),
97 [](const QStringView &, KTextEditor::View *view) {
98 return view ? QString::number(view->cursorPosition().line()) : QString();
99 },
100 false));
101 mng.addVariable(variable: Variable(
102 QStringLiteral("Document:Cursor:Column"),
103 i18n("Column number of the text cursor position in current document (starts with 0)."),
104 [](const QStringView &, KTextEditor::View *view) {
105 return view ? QString::number(view->cursorPosition().column()) : QString();
106 },
107 false));
108 mng.addVariable(variable: Variable(
109 QStringLiteral("Document:Cursor:XPos"),
110 i18n("X component in global screen coordinates of the cursor position."),
111 [](const QStringView &, KTextEditor::View *view) {
112 return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).x()) : QString();
113 },
114 false));
115 mng.addVariable(variable: Variable(
116 QStringLiteral("Document:Cursor:YPos"),
117 i18n("Y component in global screen coordinates of the cursor position."),
118 [](const QStringView &, KTextEditor::View *view) {
119 return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).y()) : QString();
120 },
121 false));
122 mng.addVariable(variable: Variable(
123 QStringLiteral("Document:Selection:Text"),
124 i18n("Text selection of the current document."),
125 [](const QStringView &, KTextEditor::View *view) {
126 return (view && view->selection()) ? view->selectionText() : QString();
127 },
128 false));
129 mng.addVariable(variable: Variable(
130 QStringLiteral("Document:Selection:StartLine"),
131 i18n("Start line of selected text of the current document."),
132 [](const QStringView &, KTextEditor::View *view) {
133 return (view && view->selection()) ? QString::number(view->selectionRange().start().line()) : QString();
134 },
135 false));
136 mng.addVariable(variable: Variable(
137 QStringLiteral("Document:Selection:StartColumn"),
138 i18n("Start column of selected text of the current document."),
139 [](const QStringView &, KTextEditor::View *view) {
140 return (view && view->selection()) ? QString::number(view->selectionRange().start().column()) : QString();
141 },
142 false));
143 mng.addVariable(variable: Variable(
144 QStringLiteral("Document:Selection:EndLine"),
145 i18n("End line of selected text of the current document."),
146 [](const QStringView &, KTextEditor::View *view) {
147 return (view && view->selection()) ? QString::number(view->selectionRange().end().line()) : QString();
148 },
149 false));
150 mng.addVariable(variable: Variable(
151 QStringLiteral("Document:Selection:EndColumn"),
152 i18n("End column of selected text of the current document."),
153 [](const QStringView &, KTextEditor::View *view) {
154 return (view && view->selection()) ? QString::number(view->selectionRange().end().column()) : QString();
155 },
156 false));
157 mng.addVariable(variable: Variable(
158 QStringLiteral("Document:RowCount"),
159 i18n("Number of rows of the current document."),
160 [](const QStringView &, KTextEditor::View *view) {
161 return view ? QString::number(view->document()->lines()) : QString();
162 },
163 false));
164 mng.addVariable(variable: Variable(
165 QStringLiteral("Document:Variable:"),
166 i18n("Read a document variable."),
167 [](const QStringView &str, KTextEditor::View *view) {
168 return view ? qobject_cast<KTextEditor::DocumentPrivate *>(object: view->document())->variable(name: str.mid(pos: 18).toString()) : QString();
169 },
170 true));
171
172 mng.addVariable(variable: Variable(
173 QStringLiteral("Date:Locale"),
174 i18n("The current date in current locale format."),
175 [](const QStringView &, KTextEditor::View *) {
176 return QLocale().toString(date: QDate::currentDate(), format: QLocale::ShortFormat);
177 },
178 false));
179 mng.addVariable(variable: Variable(
180 QStringLiteral("Date:ISO"),
181 i18n("The current date (ISO)."),
182 [](const QStringView &, KTextEditor::View *) {
183 return QDate::currentDate().toString(format: Qt::ISODate);
184 },
185 false));
186 mng.addVariable(variable: Variable(
187 QStringLiteral("Date:"),
188 i18n("The current date (QDate formatstring)."),
189 [](const QStringView &str, KTextEditor::View *) {
190 return QDate::currentDate().toString(format: str.mid(pos: 5));
191 },
192 true));
193
194 mng.addVariable(variable: Variable(
195 QStringLiteral("Time:Locale"),
196 i18n("The current time in current locale format."),
197 [](const QStringView &, KTextEditor::View *) {
198 return QLocale().toString(time: QTime::currentTime(), format: QLocale::ShortFormat);
199 },
200 false));
201 mng.addVariable(variable: Variable(
202 QStringLiteral("Time:ISO"),
203 i18n("The current time (ISO)."),
204 [](const QStringView &, KTextEditor::View *) {
205 return QTime::currentTime().toString(f: Qt::ISODate);
206 },
207 false));
208 mng.addVariable(variable: Variable(
209 QStringLiteral("Time:"),
210 i18n("The current time (QTime formatstring)."),
211 [](const QStringView &str, KTextEditor::View *) {
212 return QTime::currentTime().toString(format: str.mid(pos: 5));
213 },
214 true));
215
216 mng.addVariable(variable: Variable(
217 QStringLiteral("ENV:"),
218 i18n("Access to environment variables."),
219 [](const QStringView &str, KTextEditor::View *) {
220 return QString::fromLocal8Bit(ba: qgetenv(varName: str.mid(pos: 4).toLocal8Bit().constData()));
221 },
222 true));
223
224 mng.addVariable(variable: Variable(
225 QStringLiteral("JS:"),
226 i18n("Evaluate simple JavaScript statements."),
227 [](const QStringView &str, KTextEditor::View *) {
228 QJSEngine jsEngine;
229 const QJSValue out = jsEngine.evaluate(program: str.toString());
230 return out.toString();
231 },
232 true));
233
234 mng.addVariable(variable: Variable(
235 QStringLiteral("PercentEncoded:"),
236 i18n("Encode text to make it URL compatible."),
237 [](const QStringView &str, KTextEditor::View *) {
238 return QString::fromUtf8(ba: QUrl::toPercentEncoding(str.mid(pos: 15).toString()));
239 },
240 true));
241
242 mng.addVariable(variable: Variable(
243 QStringLiteral("UUID"),
244 i18n("Generate a new UUID."),
245 [](const QStringView &, KTextEditor::View *) {
246 return QUuid::createUuid().toString(mode: QUuid::WithoutBraces);
247 },
248 false));
249}
250
251KateVariableExpansionManager::KateVariableExpansionManager(QObject *parent)
252 : QObject(parent)
253{
254 // register default variables for expansion
255 registerVariables(mng&: *this);
256}
257
258bool KateVariableExpansionManager::addVariable(const KTextEditor::Variable &var)
259{
260 if (!var.isValid()) {
261 return false;
262 }
263
264 // reject duplicates
265 const auto alreadyExists = std::any_of(first: m_variables.begin(), last: m_variables.end(), pred: [&var](const KTextEditor::Variable &v) {
266 return var.name() == v.name();
267 });
268 if (alreadyExists) {
269 return false;
270 }
271
272 // require a ':' in prefix matches (aka %{JS:1+1})
273 if (var.isPrefixMatch() && !var.name().contains(c: QLatin1Char(':'))) {
274 return false;
275 }
276
277 m_variables.push_back(t: var);
278 return true;
279}
280
281bool KateVariableExpansionManager::removeVariable(const QString &name)
282{
283 auto it = std::find_if(first: m_variables.begin(), last: m_variables.end(), pred: [&name](const KTextEditor::Variable &var) {
284 return var.name() == name;
285 });
286 if (it != m_variables.end()) {
287 m_variables.erase(pos: it);
288 return true;
289 }
290 return false;
291}
292
293KTextEditor::Variable KateVariableExpansionManager::variable(const QString &name) const
294{
295 auto it = std::find_if(first: m_variables.begin(), last: m_variables.end(), pred: [&name](const KTextEditor::Variable &var) {
296 return var.name() == name;
297 });
298 if (it != m_variables.end()) {
299 return *it;
300 }
301 return {};
302}
303
304const QList<KTextEditor::Variable> &KateVariableExpansionManager::variables() const
305{
306 return m_variables;
307}
308
309bool KateVariableExpansionManager::expandVariable(const QString &name, KTextEditor::View *view, QString &output) const
310{
311 // first try exact matches
312 auto var = variable(name);
313 if (!var.isValid()) {
314 // try prefix matching
315 for (auto &v : m_variables) {
316 if (v.isPrefixMatch() && name.startsWith(s: v.name())) {
317 var = v;
318 break;
319 }
320 }
321 }
322
323 if (var.isValid()) {
324 output = var.evaluate(prefix: name, view);
325 return true;
326 }
327
328 return false;
329}
330
331QString KateVariableExpansionManager::expandText(const QString &text, KTextEditor::View *view)
332{
333 return KateMacroExpander::expandMacro(input: text, view);
334}
335
336void KateVariableExpansionManager::showDialog(const QList<QWidget *> &widgets, const QStringList &names) const
337{
338 // avoid any work in case no widgets or only nullptrs were provided
339 if (widgets.isEmpty() || std::all_of(first: widgets.cbegin(), last: widgets.cend(), pred: [](const QWidget *w) {
340 return w == nullptr;
341 })) {
342 return;
343 }
344
345 // collect variables
346 QList<KTextEditor::Variable> vars;
347 if (!names.isEmpty()) {
348 for (const auto &name : names) {
349 const auto var = variable(name);
350 if (var.isValid()) {
351 vars.push_back(t: var);
352 }
353 // else: Not found, silently ignore for now
354 // Maybe raise a qCWarning(LOG_KTE)?
355 }
356 } else {
357 vars = variables();
358 }
359
360 // if we have no vars at all, do nothing
361 if (vars.isEmpty()) {
362 return;
363 }
364
365 // find parent dialog (for taskbar sharing, centering, ...)
366 QWidget *parentDialog = nullptr;
367 for (auto widget : widgets) {
368 if (widget) {
369 parentDialog = widget->window();
370 break;
371 }
372 }
373
374 // show dialog
375 auto dlg = new KateVariableExpansionDialog(parentDialog);
376 for (auto widget : widgets) {
377 if (widget) {
378 dlg->addWidget(widget);
379 }
380 }
381
382 // add provided variables...
383 for (const auto &var : std::as_const(t&: vars)) {
384 if (var.isValid()) {
385 dlg->addVariable(variable: var);
386 }
387 }
388}
389
390// kate: space-indent on; indent-width 4; replace-tabs on;
391

source code of ktexteditor/src/utils/katevariableexpansionmanager.cpp