| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2004, 2010 Joseph Wenninger <jowenn@kde.org> |
| 3 | SPDX-FileCopyrightText: 2009 Milian Wolff <mail@milianw.de> |
| 4 | SPDX-FileCopyrightText: 2014 Sven Brauch <svenbrauch@gmail.com> |
| 5 | SPDX-FileCopyrightText: 2025 Mirian Margiani <mixosaurus+kde@pm.me> |
| 6 | |
| 7 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 8 | */ |
| 9 | |
| 10 | #ifndef _KATE_TEMPLATE_HANDLER_H_ |
| 11 | #define _KATE_TEMPLATE_HANDLER_H_ |
| 12 | |
| 13 | #include <QList> |
| 14 | #include <QObject> |
| 15 | #include <QPointer> |
| 16 | #include <QString> |
| 17 | |
| 18 | #include <katescript.h> |
| 19 | #include <ktexteditor/cursor.h> |
| 20 | #include <ktexteditor/range.h> |
| 21 | |
| 22 | class QDebug; |
| 23 | class KateUndoManager; |
| 24 | |
| 25 | namespace KTextEditor |
| 26 | { |
| 27 | class DocumentPrivate; |
| 28 | class ViewPrivate; |
| 29 | class MovingCursor; |
| 30 | class MovingRange; |
| 31 | class View; |
| 32 | } |
| 33 | |
| 34 | /** |
| 35 | * \brief Inserts a template and offers advanced snippet features, like navigation and mirroring. |
| 36 | * |
| 37 | * For each template inserted a new KateTemplateHandler will be created. |
| 38 | * |
| 39 | * The handler has the following features: |
| 40 | * |
| 41 | * \li It inserts the template string into the document at the requested position. |
| 42 | * \li When the template contains at least one variable, the cursor will be placed |
| 43 | * at the start of the first variable and its range gets selected. |
| 44 | * \li When more than one variable exists,TAB and SHIFT TAB can be used to navigate |
| 45 | * to the next/previous variable. |
| 46 | * \li When a variable occurs more than once in the template, edits to any of the |
| 47 | * occurrences will be mirroed to the other ones. |
| 48 | * \li When ESC is pressed, the template handler closes. |
| 49 | * \li When ALT + RETURN is pressed and a \c ${cursor} variable |
| 50 | * exists in the template,the cursor will be placed there. Else the cursor will |
| 51 | * be placed at the end of the template. |
| 52 | * |
| 53 | * \author Milian Wolff <mail@milianw.de> |
| 54 | */ |
| 55 | |
| 56 | class KateTemplateHandler : public QObject |
| 57 | { |
| 58 | Q_OBJECT |
| 59 | |
| 60 | public: |
| 61 | /** |
| 62 | * Setup the template handler, insert the template string. |
| 63 | * |
| 64 | * NOTE: The handler deletes itself when required, you do not need to |
| 65 | * keep track of it. |
| 66 | */ |
| 67 | KateTemplateHandler(KTextEditor::ViewPrivate *view, |
| 68 | KTextEditor::Cursor position, |
| 69 | const QString &templateString, |
| 70 | const QString &script, |
| 71 | KateUndoManager *undoManager); |
| 72 | |
| 73 | ~KateTemplateHandler() override; |
| 74 | |
| 75 | protected: |
| 76 | /** |
| 77 | * \brief Provide keyboard interaction for the template handler. |
| 78 | * |
| 79 | * The event filter handles the following shortcuts: |
| 80 | * |
| 81 | * TAB: jump to next editable (i.e. not mirrored) range. |
| 82 | * NOTE: this prevents indenting via TAB. |
| 83 | * SHIFT + TAB: jump to previous editable (i.e. not mirrored) range. |
| 84 | * NOTE: this prevents un-indenting via SHIFT + TAB. |
| 85 | * ESC: terminate template handler (only when no completion is active). |
| 86 | * ALT + RETURN: accept template and jump to the end-cursor. |
| 87 | * if %{cursor} was given in the template, that will be the |
| 88 | * end-cursor. |
| 89 | * else just jump to the end of the inserted text. |
| 90 | */ |
| 91 | bool eventFilter(QObject *object, QEvent *event) override; |
| 92 | |
| 93 | private: |
| 94 | /** |
| 95 | * Inserts the @p text template at @p position and performs |
| 96 | * all necessary initializations, such as populating default values |
| 97 | * and placing the cursor. |
| 98 | */ |
| 99 | void initializeTemplate(); |
| 100 | |
| 101 | /** |
| 102 | * Parse @p templateText and populate m_fields. |
| 103 | */ |
| 104 | void parseFields(const QString &templateText); |
| 105 | |
| 106 | /** |
| 107 | * Set necessary attributes (esp. background colour) on all moving |
| 108 | * ranges for the fields in m_fields. |
| 109 | */ |
| 110 | void setupFieldRanges(); |
| 111 | |
| 112 | /** |
| 113 | * Evaluate default values for all fields in m_fields and |
| 114 | * store them in the fields. This updates the @property defaultValue property |
| 115 | * of the TemplateField instances in m_fields from the raw, user-entered |
| 116 | * default value to its evaluated equivalent (e.g. "func()" -> result of function call) |
| 117 | * |
| 118 | * Default values are subsequently written into the document. |
| 119 | * |
| 120 | * @sa TemplateField |
| 121 | */ |
| 122 | void setupDefaultValues(); |
| 123 | |
| 124 | /** |
| 125 | * Install an event filter on the filter proxy of \p view for |
| 126 | * navigation between the ranges and terminating the KateTemplateHandler. |
| 127 | * |
| 128 | * \see eventFilter() |
| 129 | */ |
| 130 | void setupEventHandler(KTextEditor::View *view); |
| 131 | |
| 132 | /** |
| 133 | * Jumps to the previous editable range. If there is none, wrap and jump to the first range. |
| 134 | * |
| 135 | * \see jumpToNextRange() |
| 136 | */ |
| 137 | void jumpToPreviousRange(); |
| 138 | |
| 139 | /** |
| 140 | * Jumps to the next editable range. If there is none, wrap and jump to the last range. |
| 141 | * |
| 142 | * \see jumpToPreviousRange() |
| 143 | */ |
| 144 | void jumpToNextRange(); |
| 145 | |
| 146 | /** |
| 147 | * Helper function for jumpTo{Next,Previous} |
| 148 | * if initial is set to true, assumes the cursor is before the snippet |
| 149 | * and selects the first field |
| 150 | */ |
| 151 | void jump(int by, bool initial = false); |
| 152 | |
| 153 | /** |
| 154 | * Jumps to the final cursor position. This is either \p m_finalCursorPosition, or |
| 155 | * if that is not set, the end of \p m_templateRange. |
| 156 | */ |
| 157 | void jumpToFinalCursorPosition(); |
| 158 | |
| 159 | /** |
| 160 | * Go through all template fields and decide if their moving ranges expand |
| 161 | * when edited at the corners. Expansion is turned off if two fields are |
| 162 | * directly adjacent to avoid overlaps when characters are inserted between |
| 163 | * them. |
| 164 | */ |
| 165 | void updateRangeBehaviours(); |
| 166 | |
| 167 | private Q_SLOTS: |
| 168 | /** |
| 169 | * Saves the range of the inserted template. This is required since |
| 170 | * tabs could get expanded on insert. While we are at it, we can |
| 171 | * use it to auto-indent the code after insert. |
| 172 | */ |
| 173 | void slotTemplateInserted(KTextEditor::Document *document, KTextEditor::Range range); |
| 174 | |
| 175 | /** |
| 176 | * Install event filter on new views. |
| 177 | */ |
| 178 | void slotViewCreated(KTextEditor::Document *document, KTextEditor::View *view); |
| 179 | |
| 180 | /** |
| 181 | * Update content of all dependent fields, i.e. mirror or script fields. |
| 182 | */ |
| 183 | void updateDependentFields(KTextEditor::Document *document, KTextEditor::Range oldRange, bool textRemoved = false); |
| 184 | |
| 185 | public: |
| 186 | KTextEditor::ViewPrivate *view() const; |
| 187 | KTextEditor::DocumentPrivate *doc() const; |
| 188 | |
| 189 | private: |
| 190 | /// The view we operate on |
| 191 | KTextEditor::ViewPrivate *m_view; |
| 192 | /// The undo manager associated with our document |
| 193 | KateUndoManager *const m_undoManager; |
| 194 | |
| 195 | // Describes a single template field, e.g. ${foo}. |
| 196 | struct TemplateField { |
| 197 | // unique field ID to identify and order fields |
| 198 | qsizetype id{-1}; |
| 199 | |
| 200 | // up-to-date range for the field |
| 201 | std::shared_ptr<KTextEditor::MovingRange> range; |
| 202 | |
| 203 | // static range for identifying changes |
| 204 | KTextEditor::Range staticRange{KTextEditor::Range::invalid()}; |
| 205 | |
| 206 | // contents of the field, i.e. identifier or function to call |
| 207 | QString identifier; |
| 208 | // default value, if applicable; else empty |
| 209 | QString defaultValue; |
| 210 | enum Kind { |
| 211 | Invalid, // not an actual field |
| 212 | Editable, // normal, user-editable field (green by default) [non-dependent field] |
| 213 | Mirror, // field mirroring contents of another field [dependent field] |
| 214 | FunctionCall, // field containing the up-to-date result of a function call [dependent field] |
| 215 | FinalCursorPosition // field marking the final cursor position |
| 216 | }; |
| 217 | Kind kind = Invalid; |
| 218 | // true if this field was edited by the user before |
| 219 | bool touched = false; |
| 220 | // true if this field was removed e.g., because the line that contained it was removed |
| 221 | bool removed = false; |
| 222 | |
| 223 | bool operator==(const TemplateField &other) const |
| 224 | { |
| 225 | return id == other.id; |
| 226 | } |
| 227 | |
| 228 | friend bool operator<(const TemplateField &l, const TemplateField &r) |
| 229 | { |
| 230 | return l.id < r.id; |
| 231 | } |
| 232 | }; |
| 233 | |
| 234 | // List of all template fields in the inserted snippet. @see sortFields() |
| 235 | QList<TemplateField> m_fields; |
| 236 | |
| 237 | // Get all template fields which contain or border on @p range. |
| 238 | // If @p compareStaticRanges is @c true, compare with last saved static field |
| 239 | // ranges instead of moving field ranges. |
| 240 | const QList<TemplateField> fieldsForRange(KTextEditor::Range range, bool compareStaticRanges) const; |
| 241 | |
| 242 | // Restore order of empty adjacent fields after inserting into one. |
| 243 | void reorderEmptyAdjacentFields(const QList<TemplateField> &changedFields); |
| 244 | |
| 245 | /// Construct a map of master fields and their current value, for use in scripts. |
| 246 | KateScript::FieldMap fieldMap() const; |
| 247 | |
| 248 | /// A range that occupies the whole range of the inserted template. |
| 249 | /// When the an edit happens outside it, the template handler gets closed. |
| 250 | std::shared_ptr<KTextEditor::MovingRange> m_wholeTemplateRange; |
| 251 | |
| 252 | /// Set to true when currently updating dependent fields, to prevent recursion. |
| 253 | bool m_internalEdit; |
| 254 | |
| 255 | /// template script (i.e. javascript stuff), which can be used by the current template |
| 256 | KateScript m_templateScript; |
| 257 | }; |
| 258 | |
| 259 | #endif |
| 260 | |