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 | |