File indexing completed on 2024-04-14 03:55:31

0001 /*
0002     SPDX-FileCopyrightText: 2004, 2010 Joseph Wenninger <jowenn@kde.org>
0003     SPDX-FileCopyrightText: 2009 Milian Wolff <mail@milianw.de>
0004     SPDX-FileCopyrightText: 2014 Sven Brauch <svenbrauch@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include <QKeyEvent>
0010 #include <QQueue>
0011 #include <QRegularExpression>
0012 
0013 #include <ktexteditor/movingcursor.h>
0014 #include <ktexteditor/movingrange.h>
0015 
0016 #include "kateconfig.h"
0017 #include "katedocument.h"
0018 #include "kateglobal.h"
0019 #include "katepartdebug.h"
0020 #include "kateregexpsearch.h"
0021 #include "katetemplatehandler.h"
0022 #include "kateundomanager.h"
0023 #include "kateview.h"
0024 #include "script/katescriptmanager.h"
0025 
0026 using namespace KTextEditor;
0027 
0028 #define ifDebug(x)
0029 
0030 KateTemplateHandler::KateTemplateHandler(KTextEditor::ViewPrivate *view,
0031                                          Cursor position,
0032                                          const QString &templateString,
0033                                          const QString &script,
0034                                          KateUndoManager *undoManager)
0035     : QObject(view)
0036     , m_view(view)
0037     , m_undoManager(undoManager)
0038     , m_wholeTemplateRange()
0039     , m_internalEdit(false)
0040     , m_templateScript(script, KateScript::InputSCRIPT)
0041 {
0042     Q_ASSERT(m_view);
0043 
0044     m_templateScript.setView(m_view);
0045 
0046     // remember selection, it will be lost when inserting the template
0047     std::unique_ptr<MovingRange> selection(doc()->newMovingRange(m_view->selectionRange(), MovingRange::DoNotExpand));
0048 
0049     m_undoManager->setAllowComplexMerge(true);
0050 
0051     {
0052         connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateTemplateHandler::slotTemplateInserted);
0053         KTextEditor::Document::EditingTransaction t(doc());
0054         // insert the raw template string
0055         if (!doc()->insertText(position, templateString)) {
0056             deleteLater();
0057             return;
0058         }
0059         // now there must be a range, caught by the textInserted slot
0060         Q_ASSERT(m_wholeTemplateRange);
0061         doc()->align(m_view, *m_wholeTemplateRange);
0062     }
0063 
0064     // before initialization, restore selection (if any) so user scripts can retrieve it
0065     m_view->setSelection(selection->toRange());
0066     initializeTemplate();
0067     // then delete the selected text (if any); it was replaced by the template
0068     doc()->removeText(selection->toRange());
0069 
0070     const bool have_editable_field = std::any_of(m_fields.constBegin(), m_fields.constEnd(), [](const TemplateField &field) {
0071         return (field.kind == TemplateField::Editable);
0072     });
0073     // only do complex stuff when required
0074     if (have_editable_field) {
0075         const auto views = doc()->views();
0076         for (View *view : views) {
0077             setupEventHandler(view);
0078         }
0079 
0080         // place the cursor at the first field and select stuff
0081         jump(1, true);
0082 
0083         connect(doc(), &KTextEditor::Document::viewCreated, this, &KateTemplateHandler::slotViewCreated);
0084         connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateTemplateHandler::updateDependentFields);
0085         connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateTemplateHandler::updateDependentFields);
0086         connect(doc(), &KTextEditor::Document::aboutToReload, this, &KateTemplateHandler::deleteLater);
0087 
0088     } else {
0089         // when no interesting ranges got added, we can terminate directly
0090         jumpToFinalCursorPosition();
0091         deleteLater();
0092     }
0093 }
0094 
0095 KateTemplateHandler::~KateTemplateHandler()
0096 {
0097     m_undoManager->setAllowComplexMerge(false);
0098 }
0099 
0100 void KateTemplateHandler::sortFields()
0101 {
0102     std::sort(m_fields.begin(), m_fields.end(), [](const TemplateField &l, const TemplateField &r) {
0103         // always sort the final cursor pos last
0104         if (l.kind == TemplateField::FinalCursorPosition) {
0105             return false;
0106         }
0107         if (r.kind == TemplateField::FinalCursorPosition) {
0108             return true;
0109         }
0110         // sort by range
0111         return l.range->toRange() < r.range->toRange();
0112     });
0113 }
0114 
0115 void KateTemplateHandler::jumpToNextRange()
0116 {
0117     jump(+1);
0118 }
0119 
0120 void KateTemplateHandler::jumpToPreviousRange()
0121 {
0122     jump(-1);
0123 }
0124 
0125 void KateTemplateHandler::jump(int by, bool initial)
0126 {
0127     Q_ASSERT(by == 1 || by == -1);
0128     sortFields();
0129 
0130     // find (editable) field index of current cursor position
0131     int pos = -1;
0132     auto cursor = view()->cursorPosition();
0133     // if initial is not set, should start from the beginning (field -1)
0134     if (!initial) {
0135         pos = m_fields.indexOf(fieldForRange(KTextEditor::Range(cursor, cursor)));
0136     }
0137 
0138     // modulo field count and make positive
0139     auto wrap = [this](int x) -> unsigned int {
0140         x %= m_fields.size();
0141         return x + (x < 0 ? m_fields.size() : 0);
0142     };
0143 
0144     pos = wrap(pos);
0145     // choose field to jump to, including wrap-around
0146     auto choose_next_field = [this, by, wrap](unsigned int from_field_index) {
0147         for (int i = from_field_index + by;; i += by) {
0148             auto wrapped_i = wrap(i);
0149             auto kind = m_fields.at(wrapped_i).kind;
0150             if (kind == TemplateField::Editable || kind == TemplateField::FinalCursorPosition) {
0151                 // found an editable field by walking into the desired direction
0152                 return wrapped_i;
0153             }
0154             if (wrapped_i == from_field_index) {
0155                 // nothing found, do nothing (i.e. keep cursor in current field)
0156                 break;
0157             }
0158         }
0159         return from_field_index;
0160     };
0161 
0162     // jump
0163     auto jump_to_field = m_fields.at(choose_next_field(pos));
0164     view()->setCursorPosition(jump_to_field.range->toRange().start());
0165     if (!jump_to_field.touched) {
0166         // field was never edited by the user, so select its contents
0167         view()->setSelection(jump_to_field.range->toRange());
0168     }
0169 }
0170 
0171 void KateTemplateHandler::jumpToFinalCursorPosition()
0172 {
0173     for (const auto &field : std::as_const(m_fields)) {
0174         if (field.kind == TemplateField::FinalCursorPosition) {
0175             view()->setCursorPosition(field.range->toRange().start());
0176             return;
0177         }
0178     }
0179     view()->setCursorPosition(m_wholeTemplateRange->end());
0180 }
0181 
0182 void KateTemplateHandler::slotTemplateInserted(Document * /*document*/, Range range)
0183 {
0184     m_wholeTemplateRange.reset(doc()->newMovingRange(range, MovingRange::ExpandLeft | MovingRange::ExpandRight));
0185 
0186     disconnect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateTemplateHandler::slotTemplateInserted);
0187 }
0188 
0189 KTextEditor::DocumentPrivate *KateTemplateHandler::doc() const
0190 {
0191     return m_view->doc();
0192 }
0193 
0194 void KateTemplateHandler::slotViewCreated(Document *document, View *view)
0195 {
0196     Q_ASSERT(document == doc());
0197     Q_UNUSED(document)
0198     setupEventHandler(view);
0199 }
0200 
0201 void KateTemplateHandler::setupEventHandler(View *view)
0202 {
0203     view->focusProxy()->installEventFilter(this);
0204 }
0205 
0206 bool KateTemplateHandler::eventFilter(QObject *object, QEvent *event)
0207 {
0208     // prevent indenting by eating the keypress event for TAB
0209     if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
0210         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0211         if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) {
0212             if (!m_view->isCompletionActive()) {
0213                 return true;
0214             }
0215         }
0216     }
0217 
0218     // actually offer shortcuts for navigation
0219     if (event->type() == QEvent::ShortcutOverride) {
0220         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0221 
0222         if (keyEvent->key() == Qt::Key_Escape || (keyEvent->key() == Qt::Key_Return && keyEvent->modifiers() & Qt::AltModifier)) {
0223             // terminate
0224             jumpToFinalCursorPosition();
0225             view()->clearSelection();
0226             deleteLater();
0227             keyEvent->accept();
0228             return true;
0229         } else if (keyEvent->key() == Qt::Key_Tab && !m_view->isCompletionActive()) {
0230             if (keyEvent->modifiers() & Qt::ShiftModifier) {
0231                 jumpToPreviousRange();
0232             } else {
0233                 jumpToNextRange();
0234             }
0235             keyEvent->accept();
0236             return true;
0237         } else if (keyEvent->key() == Qt::Key_Backtab && !m_view->isCompletionActive()) {
0238             jumpToPreviousRange();
0239             keyEvent->accept();
0240             return true;
0241         }
0242     }
0243 
0244     return QObject::eventFilter(object, event);
0245 }
0246 
0247 /**
0248  * Returns an attribute with \p color as background with @p alpha alpha value.
0249  */
0250 Attribute::Ptr getAttribute(QColor color, int alpha = 230)
0251 {
0252     Attribute::Ptr attribute(new Attribute());
0253     color.setAlpha(alpha);
0254     attribute->setBackground(QBrush(color));
0255     return attribute;
0256 }
0257 
0258 void KateTemplateHandler::parseFields(const QString &templateText)
0259 {
0260     // matches any field, i.e. the three forms ${foo}, ${foo=expr}, ${func()}
0261     // this also captures escaped fields, i.e. \\${foo} etc.
0262     static const QRegularExpression field(QStringLiteral("\\\\?\\${([^}]+)}"), QRegularExpression::UseUnicodePropertiesOption);
0263     // matches the "foo=expr" form within a match of the above expression
0264     static const QRegularExpression defaultField(QStringLiteral("\\w+=([^\\}]*)"), QRegularExpression::UseUnicodePropertiesOption);
0265 
0266     // compute start cursor of a match
0267     auto startOfMatch = [this, &templateText](const QRegularExpressionMatch &match) {
0268         const auto offset = match.capturedStart(0);
0269         const auto left = QStringView(templateText).left(offset);
0270         const auto nl = QLatin1Char('\n');
0271         const auto rel_lineno = left.count(nl);
0272         const auto start = m_wholeTemplateRange->start().toCursor();
0273         return Cursor(start.line(), rel_lineno == 0 ? start.column() : 0) + Cursor(rel_lineno, offset - left.lastIndexOf(nl) - 1);
0274     };
0275 
0276     // create a moving range spanning the given field
0277     auto createMovingRangeForMatch = [this, startOfMatch](const QRegularExpressionMatch &match) {
0278         auto matchStart = startOfMatch(match);
0279         return doc()->newMovingRange({matchStart, matchStart + Cursor(0, match.capturedLength(0))}, MovingRange::ExpandLeft | MovingRange::ExpandRight);
0280     };
0281 
0282     // list of escape backslashes to remove after parsing
0283     QList<KTextEditor::Cursor> stripBackslashes;
0284     auto fieldMatch = field.globalMatch(templateText);
0285     while (fieldMatch.hasNext()) {
0286         const auto match = fieldMatch.next();
0287         if (match.captured(0).startsWith(QLatin1Char('\\'))) {
0288             // $ is escaped, not a field; mark the backslash for removal
0289             // prepend it to the list so the characters are removed starting from the
0290             // back and ranges do not move around
0291             stripBackslashes.prepend(startOfMatch(match));
0292             continue;
0293         }
0294         // a template field was found, instantiate a field object and populate it
0295         auto defaultMatch = defaultField.match(match.captured(0));
0296         const QString contents = match.captured(1);
0297         TemplateField f;
0298         f.range.reset(createMovingRangeForMatch(match));
0299         f.identifier = contents;
0300         f.kind = TemplateField::Editable;
0301         if (defaultMatch.hasMatch()) {
0302             // the field has a default value, i.e. ${foo=3}
0303             f.defaultValue = defaultMatch.captured(1);
0304             f.identifier = QStringView(contents).left(contents.indexOf(QLatin1Char('='))).trimmed().toString();
0305         } else if (f.identifier.contains(QLatin1Char('('))) {
0306             // field is a function call when it contains an opening parenthesis
0307             f.kind = TemplateField::FunctionCall;
0308         } else if (f.identifier == QLatin1String("cursor")) {
0309             // field marks the final cursor position
0310             f.kind = TemplateField::FinalCursorPosition;
0311         }
0312         for (const auto &other : std::as_const(m_fields)) {
0313             if (other.kind == TemplateField::Editable && !(f == other) && other.identifier == f.identifier) {
0314                 // field is a mirror field
0315                 f.kind = TemplateField::Mirror;
0316                 break;
0317             }
0318         }
0319         m_fields.append(f);
0320     }
0321 
0322     // remove escape characters
0323     for (const auto &backslash : stripBackslashes) {
0324         doc()->removeText(KTextEditor::Range(backslash, backslash + Cursor(0, 1)));
0325     }
0326 }
0327 
0328 void KateTemplateHandler::setupFieldRanges()
0329 {
0330     auto config = m_view->rendererConfig();
0331     auto editableAttribute = getAttribute(config->templateEditablePlaceholderColor(), 255);
0332     editableAttribute->setDynamicAttribute(Attribute::ActivateCaretIn, getAttribute(config->templateFocusedEditablePlaceholderColor(), 255));
0333     auto notEditableAttribute = getAttribute(config->templateNotEditablePlaceholderColor(), 255);
0334 
0335     // color the whole template
0336     m_wholeTemplateRange->setAttribute(getAttribute(config->templateBackgroundColor(), 200));
0337 
0338     // color all the template fields
0339     for (const auto &field : std::as_const(m_fields)) {
0340         field.range->setAttribute(field.kind == TemplateField::Editable ? editableAttribute : notEditableAttribute);
0341     }
0342 }
0343 
0344 void KateTemplateHandler::setupDefaultValues()
0345 {
0346     for (const auto &field : std::as_const(m_fields)) {
0347         if (field.kind != TemplateField::Editable) {
0348             continue;
0349         }
0350         QString value;
0351         if (field.defaultValue.isEmpty()) {
0352             // field has no default value specified; use its identifier
0353             value = field.identifier;
0354         } else {
0355             // field has a default value; evaluate it with the JS engine
0356             value = m_templateScript.evaluate(field.defaultValue).toString();
0357         }
0358         doc()->replaceText(field.range->toRange(), value);
0359     }
0360 }
0361 
0362 void KateTemplateHandler::initializeTemplate()
0363 {
0364     auto templateString = doc()->text(*m_wholeTemplateRange);
0365     parseFields(templateString);
0366     setupFieldRanges();
0367     setupDefaultValues();
0368 
0369     // call update for each field to set up the initial stuff
0370     for (int i = 0; i < m_fields.size(); i++) {
0371         auto &field = m_fields[i];
0372         ifDebug(qCDebug(LOG_KTE) << "update field:" << field.range->toRange();) updateDependentFields(doc(), field.range->toRange());
0373         // remove "user edited field" mark set by the above call since it's not a real edit
0374         field.touched = false;
0375     }
0376 }
0377 
0378 const KateTemplateHandler::TemplateField KateTemplateHandler::fieldForRange(KTextEditor::Range range) const
0379 {
0380     for (const auto &field : m_fields) {
0381         if (field.range->contains(range.start()) || field.range->end() == range.start()) {
0382             return field;
0383         }
0384         if (field.kind == TemplateField::FinalCursorPosition && range.end() == field.range->end().toCursor()) {
0385             return field;
0386         }
0387     }
0388     return {};
0389 }
0390 
0391 void KateTemplateHandler::updateDependentFields(Document *document, Range range)
0392 {
0393     Q_ASSERT(document == doc());
0394     Q_UNUSED(document);
0395     if (!m_undoManager->isActive()) {
0396         // currently undoing stuff; don't update fields
0397         return;
0398     }
0399 
0400     bool in_range = m_wholeTemplateRange->toRange().contains(range.start());
0401     bool at_end = m_wholeTemplateRange->toRange().end() == range.end() || m_wholeTemplateRange->toRange().end() == range.start();
0402     if (m_wholeTemplateRange->toRange().isEmpty() || (!in_range && !at_end)) {
0403         // edit outside template range, abort
0404         ifDebug(qCDebug(LOG_KTE) << "edit outside template range, exiting";) deleteLater();
0405         return;
0406     }
0407 
0408     if (m_internalEdit || range.isEmpty()) {
0409         // internal or null edit; for internal edits, don't do anything
0410         // to prevent unwanted recursion
0411         return;
0412     }
0413 
0414     ifDebug(qCDebug(LOG_KTE) << "text changed" << document << range;)
0415 
0416         // group all the changes into one undo transaction
0417         KTextEditor::Document::EditingTransaction t(doc());
0418 
0419     // find the field which was modified, if any
0420     sortFields();
0421     const auto changedField = fieldForRange(range);
0422     if (changedField.kind == TemplateField::Invalid) {
0423         // edit not within a field, nothing to do
0424         ifDebug(qCDebug(LOG_KTE) << "edit not within a field:" << range;) return;
0425     }
0426     if (changedField.kind == TemplateField::FinalCursorPosition && doc()->text(changedField.range->toRange()).isEmpty()) {
0427         // text changed at final cursor position: the user is done, so exit
0428         // this is not executed when the field's range is not empty: in that case this call
0429         // is for initial setup and we have to continue below
0430         ifDebug(qCDebug(LOG_KTE) << "final cursor changed:" << range;) deleteLater();
0431         return;
0432     }
0433 
0434     // turn off expanding left/right for all ranges except @p current;
0435     // this prevents ranges from overlapping each other when they are adjacent
0436     auto dontExpandOthers = [this](const TemplateField &current) {
0437         for (qsizetype i = 0; i < m_fields.size(); i++) {
0438             if (current.range != m_fields.at(i).range) {
0439                 m_fields.at(i).range->setInsertBehaviors(MovingRange::DoNotExpand);
0440             } else {
0441                 m_fields.at(i).range->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight);
0442             }
0443         }
0444     };
0445 
0446     // new contents of the changed template field
0447     const auto &newText = doc()->text(changedField.range->toRange());
0448     m_internalEdit = true;
0449     // go through all fields and update the contents of the dependent ones
0450     for (auto field = m_fields.begin(); field != m_fields.end(); field++) {
0451         if (field->kind == TemplateField::FinalCursorPosition) {
0452             // only relevant on first run
0453             doc()->replaceText(field->range->toRange(), QString());
0454         }
0455 
0456         if (*field == changedField) {
0457             // mark that the user changed this field
0458             field->touched = true;
0459         }
0460 
0461         // If this is mirrored field with the same identifier as the
0462         // changed one and the changed one is editable, mirror changes
0463         // edits to non-editable mirror fields are ignored
0464         if (field->kind == TemplateField::Mirror && changedField.kind == TemplateField::Editable && field->identifier == changedField.identifier) {
0465             // editable field changed, mirror changes
0466             dontExpandOthers(*field);
0467             doc()->replaceText(field->range->toRange(), newText);
0468         } else if (field->kind == TemplateField::FunctionCall) {
0469             // replace field by result of function call
0470             dontExpandOthers(*field);
0471             // build map of objects in the scope to pass to the function
0472             auto map = fieldMap();
0473             const auto &callResult = m_templateScript.evaluate(field->identifier, map);
0474             doc()->replaceText(field->range->toRange(), callResult.toString());
0475         }
0476     }
0477     m_internalEdit = false;
0478     updateRangeBehaviours();
0479 }
0480 
0481 void KateTemplateHandler::updateRangeBehaviours()
0482 {
0483     KTextEditor::Cursor last = {-1, -1};
0484     for (int i = 0; i < m_fields.size(); i++) {
0485         auto field = m_fields.at(i);
0486         auto end = field.range->end().toCursor();
0487         auto start = field.range->start().toCursor();
0488         if (field.kind == TemplateField::FinalCursorPosition) {
0489             // final cursor position never grows
0490             field.range->setInsertBehaviors(MovingRange::DoNotExpand);
0491         } else if (start <= last) {
0492             // ranges are adjacent, only expand to the right to prevent overlap
0493             field.range->setInsertBehaviors(MovingRange::ExpandRight);
0494         } else {
0495             // ranges are not adjacent, can grow in both directions
0496             field.range->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight);
0497         }
0498         last = end;
0499     }
0500 }
0501 
0502 KateScript::FieldMap KateTemplateHandler::fieldMap() const
0503 {
0504     KateScript::FieldMap map;
0505     for (const auto &field : m_fields) {
0506         if (field.kind != TemplateField::Editable) {
0507             // only editable fields are of interest to the scripts
0508             continue;
0509         }
0510         map.insert(field.identifier, QJSValue(doc()->text(field.range->toRange())));
0511     }
0512     return map;
0513 }
0514 
0515 KTextEditor::ViewPrivate *KateTemplateHandler::view() const
0516 {
0517     return m_view;
0518 }
0519 
0520 #include "moc_katetemplatehandler.cpp"