diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -196,8 +196,9 @@ utils/katedefaultcolors.cpp utils/katecommandrangeexpressionparser.cpp utils/katesedcmd.cpp -utils/katemacroexpander.cpp utils/variable.cpp +utils/katevariableexpansionmanager.cpp +utils/katevariableexpansionhelpers.cpp # schema schema/kateschema.cpp diff --git a/src/include/ktexteditor/editor.h b/src/include/ktexteditor/editor.h --- a/src/include/ktexteditor/editor.h +++ b/src/include/ktexteditor/editor.h @@ -23,6 +23,7 @@ #include #include +#include class KAboutData; class KConfig; @@ -293,6 +294,8 @@ */ bool unregisterVariableMatch(const QString& variable); + // TODO KF6: merge "unregisterVariableMatch()" and "unregisterVariablePrefix()" into + // a single function "unregisterVariable(const QString& name)". /** * Unregisters a prefix of variable that was previously registered with * registerVariableMatch(). @@ -313,14 +316,30 @@ */ bool expandVariable(const QString& variable, KTextEditor::View* view, QString& output) const; + // TODO KF6: turn expandText into: QString expandText(text, view) to avoid output argument /** * Expands arbitrary @p text that may contain arbitrary many variables. * On success, the expanded text is written to @p output. * * @since 5.57 */ void expandText(const QString& text, KTextEditor::View* view, QString& output) const; + /** + * Adds a QAction to the widget in @p widgets that whenever focus is + * gained. When the action is invoked, a non-modal dialog is shown that + * lists all @p variables. If @p variables is non-empty, then only the + * variables in @p variables are listed. + * + * The supported QWidgets in the @p widgets argument currently are: + * - QLineEdit + * - QTextEdit + * + * @since 5.63 + */ + void addVariableExpansion(const QVector& widgets, + const QStringList& variables = QStringList()) const; + private: /** * private d-pointer, pointing to the internal implementation diff --git a/src/utils/kateglobal.h b/src/utils/kateglobal.h --- a/src/utils/kateglobal.h +++ b/src/utils/kateglobal.h @@ -60,6 +60,7 @@ class KateAbstractInputModeFactory; class KateKeywordCompletionModel; class KateDefaultColors; +class KateVariableExpansionManager; namespace KTextEditor { @@ -422,6 +423,11 @@ */ void saveSearchReplaceHistoryModels(); + /** + * Returns the variable expansion manager. + */ + KateVariableExpansionManager *variableExpansionManager(); + Q_SIGNALS: /** * Emitted if the history of clipboard changes via copyToClipboard @@ -505,6 +511,11 @@ */ KateCmd *m_cmdManager; + /** + * variable expansion manager + */ + KateVariableExpansionManager *m_variableExpansionManager; + /** * spell check manager */ @@ -555,16 +566,6 @@ */ QStringListModel *m_searchHistoryModel; QStringListModel *m_replaceHistoryModel; - - /** - * Contains a lookup from the variable to the Variable instance. - */ - QHash m_variableExactMatches; - - /** - * Contains a lookup from the variable prefix to the Variable instance. - */ - QHash m_variablePrefixMatches; }; } diff --git a/src/utils/kateglobal.cpp b/src/utils/kateglobal.cpp --- a/src/utils/kateglobal.cpp +++ b/src/utils/kateglobal.cpp @@ -42,6 +42,7 @@ #include "spellcheck/spellcheck.h" #include "katepartdebug.h" #include "katedefaultcolors.h" +#include "katevariableexpansionmanager.h" #include "katenormalinputmodefactory.h" #include "kateviinputmodefactory.h" @@ -56,119 +57,13 @@ #include #include #include -#include -#include -#include #include #include -#include #if LIBGIT2_FOUND #include #endif -namespace { - -void registerVariables(KTextEditor::Editor * editor) -{ - editor->registerVariableMatch(QStringLiteral("Document:FileBaseName"), i18n("File base name without path and suffix of the current document."), [](const QStringView&, KTextEditor::View* view) { - const auto url = view ? view->document()->url().toLocalFile() : QString(); - return QFileInfo(url).baseName(); - }); - editor->registerVariableMatch(QStringLiteral("Document:FileExtension"), i18n("File extension of the current document."), [](const QStringView&, KTextEditor::View* view) { - const auto url = view ? view->document()->url().toLocalFile() : QString(); - return QFileInfo(url).completeSuffix(); - }); - editor->registerVariableMatch(QStringLiteral("Document:FileName"), i18n("File name without path of the current document."), [](const QStringView&, KTextEditor::View* view) { - const auto url = view ? view->document()->url().toLocalFile() : QString(); - return QFileInfo(url).fileName(); - }); - editor->registerVariableMatch(QStringLiteral("Document:FilePath"), i18n("Full path of the current document including the file name."), [](const QStringView&, KTextEditor::View* view) { - const auto url = view ? view->document()->url().toLocalFile() : QString(); - return QFileInfo(url).absoluteFilePath(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Text"), i18n("Contents of the current document."), [](const QStringView&, KTextEditor::View* view) { - return view ? view->document()->text() : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Path"), i18n("Full path of the current document excluding the file name."), [](const QStringView&, KTextEditor::View* view) { - const auto url = view ? view->document()->url().toLocalFile() : QString(); - return QFileInfo(url).absolutePath(); - }); - editor->registerVariableMatch(QStringLiteral("Document:NativeFilePath"), i18n("Full document path including file name, with native path separator (backslash on Windows)."), [](const QStringView&, KTextEditor::View* view) { - const auto url = view ? view->document()->url().toLocalFile() : QString(); - return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absoluteFilePath()); - }); - editor->registerVariableMatch(QStringLiteral("Document:NativePath"), i18n("Full document path excluding file name, with native path separator (backslash on Windows)."), [](const QStringView&, KTextEditor::View* view) { - const auto url = view ? view->document()->url().toLocalFile() : QString(); - return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absolutePath()); - }); - editor->registerVariableMatch(QStringLiteral("Document:Cursor:Line"), i18n("Line number of the text cursor position in current document (starts with 0)."), [](const QStringView&, KTextEditor::View* view) { - return view ? QString::number(view->cursorPosition().line()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Cursor:Column"), i18n("Column number of the text cursor position in current document (starts with 0)."), [](const QStringView&, KTextEditor::View* view) { - return view ? QString::number(view->cursorPosition().column()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Cursor:XPos"), i18n("X component in global screen coordinates of the cursor position."), [](const QStringView&, KTextEditor::View* view) { - return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).x()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Cursor:YPos"), i18n("Y component in global screen coordinates of the cursor position."), [](const QStringView&, KTextEditor::View* view) { - return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).y()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Selection:Text"), i18n("Text selection of the current document."), [](const QStringView&, KTextEditor::View* view) { - return (view && view->selection()) ? view->selectionText() : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Selection:StartLine"), i18n("Start line of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { - return (view && view->selection()) ? QString::number(view->selectionRange().start().line()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Selection:StartColumn"), i18n("Start column of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { - return (view && view->selection()) ? QString::number(view->selectionRange().start().column()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Selection:EndLine"), i18n("End line of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { - return (view && view->selection()) ? QString::number(view->selectionRange().end().line()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:Selection:EndColumn"), i18n("End column of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { - return (view && view->selection()) ? QString::number(view->selectionRange().end().column()) : QString(); - }); - editor->registerVariableMatch(QStringLiteral("Document:RowCount"), i18n("Number of rows of the current document."), [](const QStringView&, KTextEditor::View* view) { - return view ? QString::number(view->document()->lines()) : QString(); - }); - - editor->registerVariableMatch(QStringLiteral("Date:Locale"), i18n("The current date in current locale format."), [](const QStringView&, KTextEditor::View*) { - return QDate::currentDate().toString(Qt::DefaultLocaleShortDate); - }); - editor->registerVariableMatch(QStringLiteral("Date:ISO"), i18n("The current date (ISO)."), [](const QStringView&, KTextEditor::View*) { - return QDate::currentDate().toString(Qt::ISODate); - }); - editor->registerVariablePrefix(QStringLiteral("Date:"), i18n("The current date (QDate formatstring)."), [](const QStringView& str, KTextEditor::View*) { - return QDate::currentDate().toString(str.mid(5)); - }); - - editor->registerVariableMatch(QStringLiteral("Time:Locale"), i18n("The current time in current locale format."), [](const QStringView&, KTextEditor::View*) { - return QTime::currentTime().toString(Qt::DefaultLocaleShortDate); - }); - editor->registerVariableMatch(QStringLiteral("Time:ISO"), i18n("The current time (ISO)."), [](const QStringView&, KTextEditor::View*) { - return QTime::currentTime().toString(Qt::ISODate); - }); - editor->registerVariablePrefix(QStringLiteral("Time:"), i18n("The current time (QTime formatstring)."), [](const QStringView& str, KTextEditor::View*) { - return QTime::currentTime().toString(str.mid(5)); - }); - - editor->registerVariablePrefix(QStringLiteral("ENV:"), i18n("Access to environment variables."), [](const QStringView& str, KTextEditor::View*) { - return QString::fromLocal8Bit(qgetenv(str.mid(4).toLocal8Bit().constData())); - }); - - editor->registerVariablePrefix(QStringLiteral("JS:"), i18n("Evaluate simple JavaScript statements."), [](const QStringView& str, KTextEditor::View*) { - QJSEngine jsEngine; - const QJSValue out = jsEngine.evaluate(str.toString()); - return out.toString(); - }); - - editor->registerVariableMatch(QStringLiteral("UUID"), i18n("Generate a new UUID."), [](const QStringView&, KTextEditor::View*) { - return QUuid::createUuid().toString(QUuid::WithoutBraces); - }); -} -} - //BEGIN unit test mode static bool kateUnitTestMode = false; @@ -274,6 +169,11 @@ // m_cmdManager = new KateCmd(); + // + // variable expansion manager + // + m_variableExpansionManager = new KateVariableExpansionManager(this); + // // hl manager // @@ -332,9 +232,6 @@ // tap to QApplication object for color palette changes qApp->installEventFilter(this); - - // register default variables for expansion - registerVariables(this); } KTextEditor::EditorPrivate::~EditorPrivate() @@ -358,6 +255,10 @@ // cu model delete m_wordCompletionModel; + // delete variable expansion manager + delete m_variableExpansionManager; + m_variableExpansionManager = nullptr; + // delete the commands before we delete the cmd manager qDeleteAll(m_cmds); delete m_cmdManager; @@ -540,6 +441,11 @@ return m_cmdManager->commandList(); } +KateVariableExpansionManager *KTextEditor::EditorPrivate::variableExpansionManager() +{ + return m_variableExpansionManager; +} + void KTextEditor::EditorPrivate::updateColorPalette() { // update default color cache diff --git a/src/utils/katemacroexpander.h b/src/utils/katemacroexpander.h deleted file mode 100644 --- a/src/utils/katemacroexpander.h +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright 2019 Dominik Haumann - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#ifndef KTEXTEDITOR_MACRO_EXPANDER_H -#define KTEXTEDITOR_MACRO_EXPANDER_H - -#include - -namespace KTextEditor -{ - class View; -} - -/** - * Helper for macro expansion. - */ -namespace KateMacroExpander -{ - /** - * Expands the @p input text based on the @p view. - * @return the expanded text. - */ - QString expandMacro(const QString& input, KTextEditor::View* view); -} - -#endif // KTEXTEDITOR_MACRO_EXPANDER_H - -// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/utils/katemacroexpander.cpp b/src/utils/katemacroexpander.cpp deleted file mode 100644 --- a/src/utils/katemacroexpander.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright 2019 Dominik Haumann - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "katemacroexpander.h" - -#include - -/** - * Find closing bracket for @p str starting a position @p pos. - */ -static int findClosing(QStringView str, int pos = 0) -{ - const int len = str.size(); - int nesting = 0; - - while (pos < len) { - ++pos; - const QChar c = str[pos]; - if (c == QLatin1Char('}')) { - if (nesting == 0) { - return pos; - } - nesting--; - } else if (c == QLatin1Char('{')) { - nesting++; - } - } - return -1; -} - -QString KateMacroExpander::expandMacro(const QString& input, KTextEditor::View* view) -{ - QString output = input; - QString oldStr; - do { - oldStr = output; - const int startIndex = output.indexOf(QLatin1String("%{")); - if (startIndex < 0) { - break; - } - - const int endIndex = findClosing(output, startIndex + 2); - if (endIndex <= startIndex) { - break; - } - - const int varLen = endIndex - (startIndex + 2); - QString variable = output.mid(startIndex + 2, varLen); - variable = expandMacro(variable, view); - if (KTextEditor::Editor::instance()->expandVariable(variable, view, variable)) { - output.replace(startIndex, endIndex - startIndex + 1, variable); - } - } while (output != oldStr); // str comparison guards against infinite loop - return output; -} - -// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/utils/katevariableexpansionhelpers.h b/src/utils/katevariableexpansionhelpers.h new file mode 100644 --- /dev/null +++ b/src/utils/katevariableexpansionhelpers.h @@ -0,0 +1,101 @@ +/* This file is part of the KDE project + * + * Copyright 2019 Dominik Haumann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KTEXTEDITOR_VARIABLE_EXPANSION_HELPERS_H +#define KTEXTEDITOR_VARIABLE_EXPANSION_HELPERS_H + +#include +#include +#include +#include + +#include "variable.h" + +class QListView; +class VariableItemModel; + +namespace KTextEditor +{ + class View; + class Variable; +} + +/** + * Helper for macro expansion. + */ +namespace KateMacroExpander +{ + /** + * Expands the @p input text based on the @p view. + * @return the expanded text. + */ + QString expandMacro(const QString& input, KTextEditor::View* view); +} + +/** + * Helper dialog that shows a non-modal dialog listing all available + * variables. If the user selects a variable, the variable is inserted + * into the respective widget. + */ +class KateVariableExpansionDialog : public QDialog +{ +public: + KateVariableExpansionDialog(QWidget *parent); + + /** + * Adds @p variable to the expansion list view. + */ + void addVariable(const KTextEditor::Variable& variable); + + /** + * Returns true if no variables were added at all to the dialog. + */ + int isEmpty() const; + + /** + * Adds @p widget to the list of widgets that trigger showing this dialog. + */ + void addWidget(QWidget *widget); + +protected: + /** + * Reimplemented for the following reasons: + * - Show this dialog if one of the widgets added with addWidget() has focus. + * - Catch the resize-event for widgets (e.g. QTextEdit) where we manually + * added a clickable action in the corner + */ + bool eventFilter(QObject *watched, QEvent *event) override; + + /** + * Called whenever a widget was deleted. If all widgets are deleted, + * this dialog deletes itself via deleteLater(). + */ + void onObjectDeleted(QObject* object); + +private: + QAction *m_showAction; + QVector m_widgets; + QVector m_variables; + VariableItemModel * m_variableModel; + QListView *m_listView; +}; + +#endif // KTEXTEDITOR_VARIABLE_EXPANSION_HELPERS_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/utils/katevariableexpansionmanager.h b/src/utils/katevariableexpansionmanager.h new file mode 100644 --- /dev/null +++ b/src/utils/katevariableexpansionmanager.h @@ -0,0 +1,76 @@ +/* This file is part of the KDE project + * + * Copyright 2019 Dominik Haumann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef KTEXTEDITOR_VARIABLE_MANAGER +#define KTEXTEDITOR_VARIABLE_MANAGER + +#include +#include + +#include "variable.h" + +namespace KTextEditor +{ + class View; +} + +/** + * Manager class for variable expansion. + */ +class KateVariableExpansionManager : public QObject +{ +public: + /** + * Constructor with @p parent that takes ownership. + */ + KateVariableExpansionManager(QObject *parent); + + /** + * Adds @p variable to the expansion list view. + */ + bool addVariable(const KTextEditor::Variable& variable); + + /** + * Removes variable @p name. + */ + bool removeVariable(const QString &name); + + /** + * Returns the variable called @p name. + */ + KTextEditor::Variable variable(const QString &name) const; + + /** + * Returns all registered variables. + */ + const QVector &variables() const; + + bool expandVariable(const QString& variable, KTextEditor::View* view, QString& output) const; + + void expandText(const QString& text, KTextEditor::View* view, QString& output) const; + + void showDialog(const QVector& widgets, const QStringList& names) const; + +private: + QVector m_variables; +}; + +#endif // KTEXTEDITOR_VARIABLE_MANAGER + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/utils/katevariableexpansionmanager.cpp b/src/utils/katevariableexpansionmanager.cpp new file mode 100644 --- /dev/null +++ b/src/utils/katevariableexpansionmanager.cpp @@ -0,0 +1,273 @@ +/* This file is part of the KDE project + * + * Copyright 2019 Dominik Haumann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "katevariableexpansionmanager.h" +#include "katevariableexpansionhelpers.h" + +#include +#include "kateglobal.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static void registerVariables(KateVariableExpansionManager &mng) +{ + using KTextEditor::Variable; + + mng.addVariable(Variable(QStringLiteral("Document:FileBaseName"), i18n("File base name without path and suffix of the current document."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).baseName(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:FileExtension"), i18n("File extension of the current document."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).completeSuffix(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:FileName"), i18n("File name without path of the current document."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).fileName(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:FilePath"), i18n("Full path of the current document including the file name."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).absoluteFilePath(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Text"), i18n("Contents of the current document."), [](const QStringView&, KTextEditor::View* view) { + return view ? view->document()->text() : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Path"), i18n("Full path of the current document excluding the file name."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).absolutePath(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:NativeFilePath"), i18n("Full document path including file name, with native path separator (backslash on Windows)."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absoluteFilePath()); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:NativePath"), i18n("Full document path excluding file name, with native path separator (backslash on Windows)."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absolutePath()); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Cursor:Line"), i18n("Line number of the text cursor position in current document (starts with 0)."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->cursorPosition().line()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Cursor:Column"), i18n("Column number of the text cursor position in current document (starts with 0)."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->cursorPosition().column()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Cursor:XPos"), i18n("X component in global screen coordinates of the cursor position."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).x()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Cursor:YPos"), i18n("Y component in global screen coordinates of the cursor position."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).y()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Selection:Text"), i18n("Text selection of the current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? view->selectionText() : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Selection:StartLine"), i18n("Start line of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().start().line()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Selection:StartColumn"), i18n("Start column of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().start().column()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Selection:EndLine"), i18n("End line of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().end().line()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:Selection:EndColumn"), i18n("End column of selected text of the current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().end().column()) : QString(); + }, false)); + mng.addVariable(Variable(QStringLiteral("Document:RowCount"), i18n("Number of rows of the current document."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->document()->lines()) : QString(); + }, false)); + + mng.addVariable(Variable(QStringLiteral("Date:Locale"), i18n("The current date in current locale format."), [](const QStringView&, KTextEditor::View*) { + return QDate::currentDate().toString(Qt::DefaultLocaleShortDate); + }, false)); + mng.addVariable(Variable(QStringLiteral("Date:ISO"), i18n("The current date (ISO)."), [](const QStringView&, KTextEditor::View*) { + return QDate::currentDate().toString(Qt::ISODate); + }, false)); + mng.addVariable(Variable(QStringLiteral("Date:"), i18n("The current date (QDate formatstring)."), [](const QStringView& str, KTextEditor::View*) { + return QDate::currentDate().toString(str.mid(5)); + }, true)); + + mng.addVariable(Variable(QStringLiteral("Time:Locale"), i18n("The current time in current locale format."), [](const QStringView&, KTextEditor::View*) { + return QTime::currentTime().toString(Qt::DefaultLocaleShortDate); + }, false)); + mng.addVariable(Variable(QStringLiteral("Time:ISO"), i18n("The current time (ISO)."), [](const QStringView&, KTextEditor::View*) { + return QTime::currentTime().toString(Qt::ISODate); + }, false)); + mng.addVariable(Variable(QStringLiteral("Time:"), i18n("The current time (QTime formatstring)."), [](const QStringView& str, KTextEditor::View*) { + return QTime::currentTime().toString(str.mid(5)); + }, true)); + + mng.addVariable(Variable(QStringLiteral("ENV:"), i18n("Access to environment variables."), [](const QStringView& str, KTextEditor::View*) { + return QString::fromLocal8Bit(qgetenv(str.mid(4).toLocal8Bit().constData())); + }, true)); + + mng.addVariable(Variable(QStringLiteral("JS:"), i18n("Evaluate simple JavaScript statements."), [](const QStringView& str, KTextEditor::View*) { + QJSEngine jsEngine; + const QJSValue out = jsEngine.evaluate(str.toString()); + return out.toString(); + }, true)); + + mng.addVariable(Variable(QStringLiteral("UUID"), i18n("Generate a new UUID."), [](const QStringView&, KTextEditor::View*) { + return QUuid::createUuid().toString(QUuid::WithoutBraces); + }, false)); +} + +KateVariableExpansionManager::KateVariableExpansionManager(QObject *parent) + : QObject(parent) +{ + // register default variables for expansion + registerVariables(*this); +} + +bool KateVariableExpansionManager::addVariable(const KTextEditor::Variable& var) +{ + if (!var.isValid()) + return false; + + // reject duplicates + const auto alreadyExists = std::any_of(m_variables.begin(), m_variables.end(), [&var](const KTextEditor::Variable &v) { + return var.name() == v.name(); + }); + if (alreadyExists) { + return false; + } + + // require a ':' in prefix matches (aka %{JS:1+1}) + if (var.isPrefixMatch() && !var.name().contains(QLatin1Char(':'))) + return false; + + m_variables.push_back(var); + return true; +} + +bool KateVariableExpansionManager::removeVariable(const QString &name) +{ + auto it = std::find_if(m_variables.begin(), m_variables.end(), [&name](const KTextEditor::Variable &var) { + return var.name() == name; + }); + if (it != m_variables.end()) { + m_variables.erase(it); + return true; + } + return false; +} + +KTextEditor::Variable KateVariableExpansionManager::variable(const QString &name) const +{ + auto it = std::find_if(m_variables.begin(), m_variables.end(), [&name](const KTextEditor::Variable &var) { + return var.name() == name; + }); + if (it != m_variables.end()) { + return *it; + } + return {}; +} + +const QVector &KateVariableExpansionManager::variables() const +{ + return m_variables; +} + +bool KateVariableExpansionManager::expandVariable(const QString& name, KTextEditor::View* view, QString& output) const +{ + // first try exact matches + auto var = variable(name); + if (!var.isValid()) { + // try prefix matching + const int colonIndex = name.indexOf(QLatin1Char(':')); + if (colonIndex >= 0) { + var = variable(name.left(colonIndex + 1)); + } + } + + if (var.isValid()) { + output = var.evaluate(name, view); + return true; + } + + return false; +} + +void KateVariableExpansionManager::expandText(const QString& text, KTextEditor::View* view, QString& output) const +{ + output = KateMacroExpander::expandMacro(text, view); +} + +void KateVariableExpansionManager::showDialog(const QVector& widgets, const QStringList& names) const +{ + // avoid any work in case no widgets or only nullptrs were provided + if (widgets.isEmpty() || std::all_of(widgets.cbegin(), widgets.cend(), [](const QWidget* w) { return w == nullptr; })) { + return; + } + + // collect variables + QVector vars; + if (!names.isEmpty()) { + for (const auto & name : names) { + const auto var = variable(name); + if (var.isValid()) { + vars.push_back(var); + } + // else: Not found, silently ignore for now + // Maybe raise a qWarning()? + } + } else { + vars = variables(); + } + + // if we have no vars at all, do nothing + if (vars.isEmpty()) { + return; + } + + // find parent dialog (for taskbar sharing, centering, ...) + QWidget *parentDialog = nullptr; + for (auto widget : widgets) { + if (widget) { + parentDialog = widget->window(); + break; + } + } + + // show dialog + auto dlg = new KateVariableExpansionDialog(parentDialog); + for (auto widget : widgets) { + if (widget) { + dlg->addWidget(widget); + } + } + + // add provided variables... + for (const auto & var : vars) { + if (var.isValid()) { + dlg->addVariable(var); + } + } +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/utils/ktexteditor.cpp b/src/utils/ktexteditor.cpp --- a/src/utils/ktexteditor.cpp +++ b/src/utils/ktexteditor.cpp @@ -41,14 +41,14 @@ #include "sessionconfiginterface.h" #include "texthintinterface.h" #include "variable.h" +#include "katevariableexpansionmanager.h" #include "annotationinterface.h" #include "abstractannotationitemdelegate.h" #include "kateglobal.h" #include "kateconfig.h" #include "katecmd.h" -#include "katemacroexpander.h" using namespace KTextEditor; @@ -107,76 +107,39 @@ bool Editor::registerVariableMatch(const QString& name, const QString& description, ExpandFunction expansionFunc) { - if (name.isEmpty() || expansionFunc == nullptr) - return false; - - if (d->m_variableExactMatches.contains(name)) - return false; - - d->m_variableExactMatches.insert(name, Variable(name, description, expansionFunc)); - return true; + const auto var = Variable(name, description, expansionFunc, false); + return d->variableExpansionManager()->addVariable(var); } bool Editor::registerVariablePrefix(const QString& prefix, const QString& description, ExpandFunction expansionFunc) { - if (prefix.isEmpty() || expansionFunc == nullptr) - return false; - - if (d->m_variablePrefixMatches.contains(prefix)) - return false; - - if (!prefix.contains(QLatin1Char(':'))) - return false; - - d->m_variablePrefixMatches.insert(prefix, Variable(prefix, description, expansionFunc)); - return true; + const auto var = Variable(prefix, description, expansionFunc, true); + return d->variableExpansionManager()->addVariable(var); } bool Editor::unregisterVariableMatch(const QString& variable) { - auto it = d->m_variableExactMatches.find(variable); - if (it != d->m_variableExactMatches.end()) { - d->m_variableExactMatches.erase(it); - return true; - } - return false; + return d->variableExpansionManager()->removeVariable(variable); } bool Editor::unregisterVariablePrefix(const QString& variable) { - auto it = d->m_variablePrefixMatches.find(variable); - if (it != d->m_variablePrefixMatches.end()) { - d->m_variablePrefixMatches.erase(it); - return true; - } - return false; + return d->variableExpansionManager()->removeVariable(variable); } bool Editor::expandVariable(const QString& variable, KTextEditor::View* view, QString& output) const { - // first try exact matches - const auto it = d->m_variableExactMatches.find(variable); - if (it != d->m_variableExactMatches.end()) { - output = it->evaluate(variable, view); - return true; - } - - // try prefix matching - const int colonIndex = variable.indexOf(QLatin1Char(':')); - if (colonIndex >= 0) { - const QString prefix = variable.left(colonIndex + 1); - const auto itPrefix = d->m_variablePrefixMatches.find(prefix); - if (itPrefix != d->m_variablePrefixMatches.end()) { - output = itPrefix->evaluate(variable, view); - return true; - } - } - return false; + return d->variableExpansionManager()->expandVariable(variable, view, output); } void Editor::expandText(const QString& text, KTextEditor::View* view, QString& output) const { - output = KateMacroExpander::expandMacro(text, view); + d->variableExpansionManager()->expandText(text, view, output); +} + +void Editor::addVariableExpansion(const QVector& widgets, const QStringList& variables) const +{ + d->variableExpansionManager()->showDialog(widgets, variables); } bool View::insertText(const QString &text) diff --git a/src/utils/variable.h b/src/utils/variable.h --- a/src/utils/variable.h +++ b/src/utils/variable.h @@ -53,21 +53,43 @@ */ using ExpandFunction = QString (*)(const QStringView& text, KTextEditor::View* view); + /** + * Constructs an invalid Variable, see isValid(). + */ + Variable() = default; + /** * Constructor defining a Variable by its @p name, its @p description, and * its function @p expansionFunc to expand a variable to its corresponding - * value. + * value. The parameter @p isPrefixMatch indicates whether this Variable + * represents an exact match (false) or a prefix match (true). * * @note The @p name should @e not be translated. */ - Variable(const QString& name, const QString& description, ExpandFunction expansionFunc); + Variable(const QString& name, const QString& description, ExpandFunction expansionFunc, bool isPrefixMatch); + + /** + * Copy constructor. + */ + Variable(const Variable & copy) = default; + + /** + * Assignment operator. + */ + Variable & operator=(const Variable & copy) = default; /** * Returns true, if the name is non-empty and the function provided in the * constructor is not a nullptr. */ bool isValid() const; + /** + * Returns whether this Variable represents an exact match (false) or a + * prefix match (true). + */ + bool isPrefixMatch() const; + /** * Returns the @p name that was provided in the constructor. * Depending on where the Variable is registered, this name is used to @@ -100,6 +122,7 @@ QString m_name; QString m_description; ExpandFunction m_function; + bool m_isPrefixMatch = false; }; } diff --git a/src/utils/variable.cpp b/src/utils/variable.cpp --- a/src/utils/variable.cpp +++ b/src/utils/variable.cpp @@ -21,17 +21,23 @@ namespace KTextEditor { -Variable::Variable(const QString& name, const QString& description, Variable::ExpandFunction func) +Variable::Variable(const QString& name, const QString& description, Variable::ExpandFunction func, bool isPrefixMatch) : m_name(name) , m_description(description) , m_function(func) + , m_isPrefixMatch(isPrefixMatch) {} bool Variable::isValid() const { return (!m_name.isEmpty()) && (m_function != nullptr); } +bool Variable::isPrefixMatch() const +{ + return m_isPrefixMatch; +} + QString Variable::name() const { return m_name;