File indexing completed on 2024-04-28 11:45:35
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 <QQueue> 0010 #include <QRegularExpression> 0011 0012 #include <ktexteditor/movingcursor.h> 0013 #include <ktexteditor/movingrange.h> 0014 0015 #include "kateconfig.h" 0016 #include "katedocument.h" 0017 #include "kateglobal.h" 0018 #include "katepartdebug.h" 0019 #include "kateregexpsearch.h" 0020 #include "katerenderer.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 QVector<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->renderer()->config(); 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 ¤t) { 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"