Kate
katetemplatehandler.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries and the Kate part. 00002 * 00003 * Copyright (C) 2004,2010 Joseph Wenninger <jowenn@kde.org> 00004 * Copyright (C) 2009 Milian Wolff <mail@milianw.de> 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2 of the License, or (at your option) any later version. 00010 * 00011 * This library is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 * Library General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Library General Public License 00017 * along with this library; see the file COPYING.LIB. If not, write to 00018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 * Boston, MA 02110-1301, USA. 00020 */ 00021 00022 #include <QtCore/QQueue> 00023 00024 #include <ktexteditor/movingcursor.h> 00025 #include <ktexteditor/movingrange.h> 00026 00027 #include "katetemplatehandler.h" 00028 #include "katedocument.h" 00029 #include "kateview.h" 00030 #include "kateconfig.h" 00031 #include "katerenderer.h" 00032 #include "kateundomanager.h" 00033 #include "kateregexpsearch.h" 00034 #include "kateglobal.h" 00035 #include "script/katetemplatescript.h" 00036 #include "script/katescriptmanager.h" 00037 00038 using namespace KTextEditor; 00039 00040 #define ifDebug(x) 00041 00042 static bool cmp_moving_ranges(const KTextEditor::MovingRange* r1, const KTextEditor::MovingRange* r2) 00043 { 00044 return r1->start() < r2->start(); 00045 } 00046 00048 static bool customContains(MovingRange* range, const Cursor& cursor) 00049 { 00050 return range->start() <= cursor && range->end() >= cursor; 00051 } 00052 00053 static bool customContains(const KTextEditor::Range &range, const Cursor& cursor) 00054 { 00055 return range.start() <= cursor && range.end() >= cursor; 00056 } 00057 00058 /* ####################################### */ 00059 00060 KateTemplateHandler::KateTemplateHandler(KateView *view, 00061 const Cursor& position, 00062 const QString &templateString, 00063 const QMap<QString, QString> &initialValues, 00064 KateUndoManager* undoManager, 00065 KateTemplateScript* templateScript) 00066 : QObject(view) 00067 , m_view(view) 00068 , m_undoManager(undoManager) 00069 , m_wholeTemplateRange(0) 00070 , m_finalCursorPosition(0) 00071 , m_lastCaretPosition(position) 00072 , m_isMirroring(false) 00073 , m_editWithUndo(false) 00074 , m_jumping(false) 00075 , m_templateScript(templateScript) 00076 { 00077 ifDebug(kDebug() << templateString << initialValues;) 00078 00079 QMap<QString, QString> initial_Values(initialValues); 00080 00081 if (initial_Values.contains("selection")) { 00082 if (initial_Values["selection"].isEmpty()) { 00083 Q_ASSERT(m_view); 00084 initial_Values[ "selection" ] = m_view->selectionText(); 00085 } 00086 } 00087 00088 if (m_view && m_view->selection()) { 00089 m_lastCaretPosition = m_view->selectionRange().start(); 00090 m_view->removeSelectionText(); 00091 } 00092 00093 ifDebug(kDebug() << initial_Values;) 00094 00095 connect(doc(), SIGNAL(aboutToReload(KTextEditor::Document*)), 00096 this, SLOT(cleanupAndExit())); 00097 00098 connect(doc(), SIGNAL(textInserted(KTextEditor::Document*, KTextEditor::Range)), 00099 this, SLOT(slotTemplateInserted(KTextEditor::Document*, KTextEditor::Range))); 00100 00102 doc()->editStart(); 00103 00104 if (doc()->insertText(m_lastCaretPosition, templateString)) { 00105 Q_ASSERT(m_wholeTemplateRange); 00106 00107 if (m_view) { 00108 // indent the inserted template properly, this makes it possible 00109 // to share snippets e.g. via GHNS without caring about 00110 // what indent-style to use. 00111 doc()->align(m_view, *m_wholeTemplateRange); 00112 } 00113 } 00114 00120 00121 handleTemplateString(initial_Values); 00122 m_undoManager->undoSafePoint(); 00123 doc()->editEnd(); 00124 00125 if (!initialValues.isEmpty() && m_view) { 00126 // only do complex stuff when required 00127 if (!m_templateRanges.isEmpty()) { 00128 foreach(View* view, doc()->views()) { 00129 setupEventHandler(view); 00130 } 00131 00132 connect(doc(), SIGNAL(viewCreated(KTextEditor::Document*, KTextEditor::View*)), 00133 this, SLOT(slotViewCreated(KTextEditor::Document*, KTextEditor::View*))); 00134 connect(doc(), SIGNAL(textInserted(KTextEditor::Document*, KTextEditor::Range)), 00135 this, SLOT(slotTextChanged(KTextEditor::Document*, KTextEditor::Range))); 00136 connect(doc(), SIGNAL(textRemoved(KTextEditor::Document*, KTextEditor::Range)), 00137 this, SLOT(slotTextChanged(KTextEditor::Document*, KTextEditor::Range))); 00138 00139 setEditWithUndo(undoManager->isActive()); 00140 00141 connect(undoManager, SIGNAL(isActiveChanged(bool)), 00142 this, SLOT(setEditWithUndo(bool))); 00143 00144 } else { 00145 // when no interesting ranges got added, we can terminate directly 00146 jumpToFinalCursorPosition(); 00147 cleanupAndExit(); 00148 } 00149 00150 } else { 00151 cleanupAndExit(); 00152 } 00153 } 00154 00155 KateTemplateHandler::~KateTemplateHandler() 00156 { 00157 } 00158 00159 void KateTemplateHandler::slotTemplateInserted(Document *document, const Range& range) 00160 { 00161 Q_ASSERT(document == doc()); 00162 Q_UNUSED(document); 00163 ifDebug(kDebug() << "template range inserted" << range;) 00164 00165 m_wholeTemplateRange = doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); 00166 00167 disconnect(doc(), SIGNAL(textInserted(KTextEditor::Document*, KTextEditor::Range)), 00168 this, SLOT(slotTemplateInserted(KTextEditor::Document*, KTextEditor::Range))); 00169 } 00170 00171 void KateTemplateHandler::cleanupAndExit() 00172 { 00173 ifDebug(kDebug() << "cleaning up and exiting";) 00174 disconnect(doc(), SIGNAL(viewCreated(KTextEditor::Document*, KTextEditor::View*)), 00175 this, SLOT(slotViewCreated(KTextEditor::Document*, KTextEditor::View*))); 00176 disconnect(doc(), SIGNAL(textInserted(KTextEditor::Document*, KTextEditor::Range)), 00177 this, SLOT(slotTextChanged(KTextEditor::Document*, KTextEditor::Range))); 00178 disconnect(doc(), SIGNAL(textRemoved(KTextEditor::Document*, KTextEditor::Range)), 00179 this, SLOT(slotTextChanged(KTextEditor::Document*, KTextEditor::Range))); 00180 00181 if (!m_templateRanges.isEmpty()) { 00182 foreach(MovingRange* range, m_templateRanges) { 00183 // delete all children 00184 foreach(MovingRange* child, m_templateRangesChildren[range]) 00185 delete child; 00186 00187 delete range; 00188 } 00189 00190 m_templateRanges.clear(); 00191 m_templateRangesChildren.clear(); 00192 m_templateRangesChildToParent.clear(); 00193 } 00194 00195 // no children if no ranges around 00196 Q_ASSERT(m_templateRangesChildren.isEmpty()); 00197 Q_ASSERT(m_templateRangesChildToParent.isEmpty()); 00198 00199 if (!m_spacersMovingRanges.isEmpty()) { 00200 foreach(MovingRange* range, m_spacersMovingRanges) { 00201 doc()->removeText(*range); 00202 delete range; 00203 } 00204 00205 m_spacersMovingRanges.clear(); 00206 } 00207 00208 delete m_wholeTemplateRange; 00209 delete m_finalCursorPosition; 00210 delete this; 00211 } 00212 00213 void KateTemplateHandler::jumpToFinalCursorPosition() 00214 { 00215 if (m_view && (!m_wholeTemplateRange 00216 || customContains(m_wholeTemplateRange->toRange(), m_view->cursorPosition()))) { 00217 m_view->setSelection(Range::invalid()); 00218 m_view->setCursorPosition(*m_finalCursorPosition); 00219 } 00220 } 00221 00222 KateDocument *KateTemplateHandler::doc() 00223 { 00224 return m_view->doc(); 00225 } 00226 00227 void KateTemplateHandler::setEditWithUndo(const bool &enabled) 00228 { 00229 m_editWithUndo = enabled; 00230 } 00231 00232 void KateTemplateHandler::slotViewCreated(Document* document, View* view) 00233 { 00234 Q_ASSERT(document == doc()); 00235 Q_UNUSED(document) 00236 setupEventHandler(view); 00237 } 00238 00239 void KateTemplateHandler::setupEventHandler(View* view) 00240 { 00241 view->focusProxy()->installEventFilter(this); 00242 } 00243 00244 bool KateTemplateHandler::eventFilter(QObject* object, QEvent* event) 00245 { 00246 // prevent indenting by eating the keypress event for TAB 00247 if (event->type() == QEvent::KeyPress) { 00248 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); 00249 00250 if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) { 00251 if (!m_view->isCompletionActive()) { 00252 return true; 00253 } 00254 } 00255 } 00256 00257 // actually offer shortcuts for navigation 00258 00259 if (event->type() == QEvent::ShortcutOverride) { 00260 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); 00261 00262 if (keyEvent->key() == Qt::Key_Return && keyEvent->modifiers() & Qt::AltModifier) { 00263 // terminate 00264 jumpToFinalCursorPosition(); 00265 cleanupAndExit(); 00266 keyEvent->accept(); 00267 return true; 00268 00269 } else if (keyEvent->key() == Qt::Key_Escape) { 00270 if (!m_view || !m_view->selection()) { 00271 // terminate 00272 jumpToFinalCursorPosition(); 00273 cleanupAndExit(); 00274 keyEvent->accept(); 00275 return true; 00276 } 00277 00278 } else if (keyEvent->key() == Qt::Key_Tab && !m_view->isCompletionActive()) { 00279 if (keyEvent->modifiers() & Qt::Key_Shift) { 00280 jumpToPreviousRange(); 00281 00282 } else { 00283 jumpToNextRange(); 00284 } 00285 00286 keyEvent->accept(); 00287 return true; 00288 00289 } else if (keyEvent->key() == Qt::Key_Backtab && !m_view->isCompletionActive()) { 00290 jumpToPreviousRange(); 00291 keyEvent->accept(); 00292 return true; 00293 } 00294 } 00295 00296 return QObject::eventFilter(object, event); 00297 } 00298 00299 void KateTemplateHandler::jumpToPreviousRange() 00300 { 00301 const Cursor & cursor = m_view->cursorPosition(); 00302 00303 if (cursor == *m_finalCursorPosition) { 00304 // wrap and jump to last range 00305 setCurrentRange(m_masterRanges.last()); 00306 return; 00307 } 00308 00309 MovingRange* previousRange = 0; 00310 00311 foreach(MovingRange* range, m_masterRanges) { 00312 if (range->start() >= cursor) { 00313 continue; 00314 } 00315 00316 if (!previousRange || range->start() > previousRange->start()) { 00317 previousRange = range; 00318 00319 if (m_templateRangesChildToParent.value(previousRange)) previousRange = m_templateRangesChildToParent.value(previousRange); 00320 } 00321 } 00322 00323 if (previousRange) { 00324 setCurrentRange(previousRange); 00325 00326 } else { 00327 // wrap and jump to final cursor 00328 jumpToFinalCursorPosition(); 00329 } 00330 } 00331 00332 void KateTemplateHandler::jumpToNextRange() 00333 { 00334 const Cursor & cursor = m_view->cursorPosition(); 00335 00336 if (cursor == *m_finalCursorPosition) { 00337 // wrap and jump to first range 00338 setCurrentRange(m_masterRanges.first()); 00339 return; 00340 } 00341 00342 MovingRange* nextRange = 0; 00343 00344 foreach(MovingRange* range, m_masterRanges) { 00345 if (range->start() <= cursor) { 00346 continue; 00347 } 00348 00349 if (!nextRange || range->start() < nextRange->start()) { 00350 nextRange = range; 00351 } 00352 } 00353 00354 if (nextRange) { 00355 if (m_templateRangesChildToParent.value(nextRange)) nextRange = m_templateRangesChildToParent.value(nextRange); 00356 00357 setCurrentRange(nextRange); 00358 00359 } else { 00360 // wrap and jump to final cursor 00361 jumpToFinalCursorPosition(); 00362 } 00363 } 00364 00365 void KateTemplateHandler::setCurrentRange(MovingRange* range) 00366 { 00367 if (!m_templateRangesChildren[range].isEmpty()) { 00368 ifDebug(kDebug() << "looking for mirroring range";) 00369 // jump to first mirrored range 00370 bool found = false; 00371 foreach(MovingRange* childRange, m_templateRangesChildren[range]) { 00372 ifDebug(kDebug() << "checking range equality";) 00373 00374 if (m_masterRanges.contains(childRange)) { 00375 ifDebug(kDebug() << "found master range";) 00376 range = childRange; 00377 found = true; 00378 break; 00379 } 00380 } 00381 00382 if (!found) { 00383 range = m_templateRangesChildren[range].first(); 00384 } 00385 } 00386 00387 if (m_view) { 00388 m_jumping = true; 00389 00390 if (m_uneditedRanges.contains(range)) { 00391 m_view->setSelection(*range); 00392 } 00393 00394 m_view->setCursorPosition(range->start()); 00395 00396 m_jumping = false; 00397 } 00398 00399 m_lastCaretPosition = range->start(); 00400 } 00401 00405 Attribute::Ptr getAttribute(QColor color, int alpha = 230) 00406 { 00407 Attribute::Ptr attribute(new Attribute()); 00408 color.setAlpha(alpha); 00409 attribute->setBackground(QBrush(color)); 00410 return attribute; 00411 } 00412 00413 void KateTemplateHandler::handleTemplateString(const QMap< QString, QString >& initialValues) 00414 { 00415 QString templateString = doc()->text(*m_wholeTemplateRange); 00416 00417 int line = m_wholeTemplateRange->start().line(); 00418 int column = m_wholeTemplateRange->start().column(); 00419 00420 // not equal -1 when we found a start position 00421 int startPos = -1; 00422 00423 bool lastWasBrace = false; 00424 00425 // each found variable gets it's range(s) added to the list. 00426 // the key is the varname, e.g. the same as in initialValues 00427 // to be able to iterate over them in a FIFO matter, also store 00428 // the keys in a queue. 00429 QQueue<QString> keyQueue; 00430 QMultiMap<QString, Range> ranges; 00431 QMap<Range, MirrorBehaviour> mirrorBehaviourBuildHelper; 00432 00433 00434 QList<Range> spacers; 00435 // valid, if we find an occurrence of ${cursor} 00436 Cursor finalCursorPosition = Cursor::invalid(); 00437 00438 // parse string for ${VAR} or %{VAR} 00439 // VAR must not contain $ or % 00440 // VAR must not contain newlines 00441 // VAR must be set as key in initialValues 00442 // expression must not be escaped 00443 00444 for (int i = 0; i < templateString.size(); ++i) { 00445 ifDebug(kDebug() << "checking character:" << templateString[i];) 00446 00447 if (templateString[i] == '\n') { 00448 lastWasBrace = false; 00449 ++line; 00450 column = 0; 00451 00452 if (startPos != -1) { 00453 // don't allow variables to span multiple lines 00454 startPos = -1; 00455 } 00456 00457 } else if ((templateString[i] == '%' || templateString[i] == '$') 00458 && i + 1 < templateString.size() && templateString[i+1] == '{') { 00459 00460 // check whether this var is escaped 00461 int escapeChars = 0; 00462 00463 while (i - escapeChars > 0 && templateString[i - escapeChars - 1] == '\\') { 00464 ++escapeChars; 00465 } 00466 00467 if (escapeChars > 0) { 00468 ifDebug(kDebug() << "found" << escapeChars << "escape chars at " << templateString.mid(i - escapeChars - 10, escapeChars + 10);) 00469 // remove half of the escape chars (i.e. \\ => \) and make sure the 00470 // odd rest is removed as well (i.e. the one that escapes this var) 00471 int toRemove = (escapeChars + 1) / 2; 00472 ifDebug(kDebug() << "will remove" << toRemove << "of those escape chars";) 00473 templateString.remove(i - escapeChars, toRemove); 00474 i -= toRemove; 00475 column -= toRemove; 00476 } 00477 00478 if (escapeChars % 2 == 0) { 00479 // don't check for startPos == -1 here, overwrite blindly since nested variables are not supported 00480 if (lastWasBrace) { 00481 templateString.insert(i, " "); 00482 spacers.append(Range(line, column, line, column + 1)); 00483 ++i; 00484 ++column; 00485 } 00486 00487 startPos = i; 00488 } 00489 00490 lastWasBrace = false; 00491 00492 // skip '{' 00493 ++i; 00494 column += 2; 00495 00496 } else if ((startPos != -1) && (templateString[i] == ':')) { // skip init value, handled by KTE 00497 i++; 00498 column++; 00499 int backslash_count = 0; 00500 00501 for (;i < templateString.size();i++, column++) { 00502 if (templateString[i] == '\n') { 00503 ++line; 00504 column = 0; 00505 00506 if (startPos != -1) { 00507 // don't allow variables to span multiple lines 00508 startPos = -1; 00509 } 00510 00511 break; 00512 } 00513 00514 if (templateString[i] == '}') { 00515 if ((backslash_count % 2) == 0) { 00516 i--; 00517 //column--; 00518 break; 00519 00520 } else { 00521 backslash_count = 0; 00522 } 00523 00524 } else if (templateString[i] == '\\') { 00525 backslash_count++; 00526 00527 } else { // any character teminates a backslash sequence 00528 backslash_count = 0; 00529 } 00530 } 00531 00532 } else if ((startPos != -1) && (templateString[i] == '/')) { // skip regexp 00533 i++; 00534 column++; 00535 int backslash_count = 0; 00536 int slashcount = 1; 00537 00538 for (;i < templateString.size();i++, column++) { 00539 if (templateString[i] == '\n') { 00540 ++line; 00541 column = 0; 00542 00543 if (startPos != -1) { 00544 // don't allow variables to span multiple lines 00545 startPos = -1; 00546 } 00547 00548 break; 00549 } 00550 00551 if (templateString[i] == '/') { 00552 if ((backslash_count % 2) == 0) 00553 slashcount++; 00554 00555 backslash_count = 0; 00556 00557 } else if (templateString[i] == '\\') { 00558 backslash_count++; 00559 00560 } else { // any character teminates a backslash sequence 00561 backslash_count = 0; 00562 } 00563 00564 if (slashcount == 3) { 00565 column++; 00566 break; 00567 } 00568 } 00569 00570 } else if (templateString[i] == '}' && startPos != -1) { 00571 lastWasBrace = true; 00572 bool force_first = false; 00573 // get key, i.e. contents between ${..} 00574 QString key = templateString.mid(startPos + 2, i - (startPos + 2)); 00575 int keyLength = key.length(); 00576 QString searchReplace; 00577 ifDebug(kDebug() << "key found:" << key;) 00578 bool check_slash = false; 00579 bool check_colon = false; 00580 bool check_backtick = false; 00581 int pos_slash = key.indexOf("/"); 00582 int pos_colon = key.indexOf(":"); 00583 int pos_backtick = key.indexOf("`"); 00584 00585 if ((pos_slash == -1) && (pos_colon == -1)) { 00586 // do nothing 00587 } else if ((pos_slash != -1) && (pos_colon == -1)) { 00588 check_slash = true; 00589 } else if ((pos_slash == -1) && (pos_colon != -1)) { 00590 check_colon = true; 00591 } else { 00592 if (pos_colon < pos_slash) { 00593 check_colon = true; 00594 } else { 00595 check_slash = true; 00596 } 00597 } 00598 00599 if (!check_slash && !check_colon && pos_backtick >= 0) { 00600 check_backtick = true; 00601 } 00602 00603 QString functionName; 00604 00605 if (check_slash) { 00606 searchReplace = key.mid(pos_slash + 1); 00607 key = key.left(pos_slash); 00608 ifDebug(kDebug() << "search_replace" << searchReplace;) 00609 } else if (check_colon) { 00610 key = key.left(pos_colon); 00611 ifDebug(kDebug() << "real key found:" << key;) 00612 } else if (check_backtick) { 00613 functionName = key.mid(pos_backtick + 1); 00614 functionName = functionName.left(functionName.indexOf("`")); 00615 key = key.left(pos_backtick); 00616 ifDebug(kDebug() << "real key found:" << key << "function:" << functionName;) 00617 } 00618 00619 if (key.contains("@")) { 00620 key = key.left(key.indexOf("@")); 00621 force_first = true; 00622 } 00623 00624 ifDebug(kDebug() << "real key found:" << key;) 00625 00626 if (!initialValues.contains(key)) { 00627 kWarning() << "unknown variable key:" << key; 00628 } else if (key == "cursor") { 00629 finalCursorPosition = Cursor(line, column - keyLength - 2); 00630 // don't insert anything, just remove the placeholder 00631 templateString.remove(startPos, i - startPos + 1); 00632 // correct iterator pos, 3 == $ + { + } 00633 i -= 3 + keyLength; 00634 column -= 2 + keyLength; 00635 startPos = -1; 00636 } else { 00637 MirrorBehaviour behaviour; 00638 00639 if (!searchReplace.isEmpty()) { 00640 QString search; 00641 bool searchValid = false; 00642 QString replace; 00643 bool replaceValid = false; 00644 QString flags; 00645 //search part; 00646 00647 while (!searchReplace.isEmpty()) { 00648 ifDebug(kDebug() << "searchReplace=" << searchReplace;) 00649 int regescapes = 0; 00650 int pos = searchReplace.indexOf("/"); 00651 00652 for (int epos = pos - 1; epos >= 0 && searchReplace.at(epos) == '\\'; epos--) { 00653 regescapes++; 00654 } 00655 00656 ifDebug(kDebug() << "regescapes=" << regescapes;) 00657 if ((regescapes % 2) == 1) { 00658 search += searchReplace.left(pos + 1); 00659 searchReplace = searchReplace.mid(pos + 1); 00660 ifDebug(kDebug() << "intermediate search string is=" << search;) 00661 } else { 00662 search += searchReplace.left(pos); 00663 searchReplace = searchReplace.mid(pos + 1); 00664 searchValid = true; 00665 ifDebug(kDebug() << "final search string is=" << search;) 00666 ifDebug(kDebug() << "remaining characters in searchReplace" << searchReplace;) 00667 break; 00668 } 00669 } 00670 00671 //replace part 00672 00673 if (searchValid) { 00674 int last_slash = searchReplace.lastIndexOf("/"); 00675 00676 if (last_slash != -1) { 00677 replace = searchReplace.left(last_slash); 00678 replaceValid = true; 00679 flags = searchReplace.mid(last_slash + 1); 00680 } 00681 } 00682 00683 if (searchValid && replaceValid) { 00684 behaviour = MirrorBehaviour(search, replace, flags); 00685 } 00686 00687 } else if (!functionName.isEmpty()) { 00688 behaviour = MirrorBehaviour(m_templateScript, functionName, this); 00689 } 00690 00691 const QString initialVal = behaviour.getMirrorString(initialValues[key]); 00692 00693 // whether the variable starts with % or $ 00694 QChar c = templateString[startPos]; 00695 00696 // replace variable with initial value 00697 templateString.replace(startPos, i - startPos + 1, initialVal); 00698 00699 // correct iterator pos, 3 == % + { + } 00700 i -= 3 + keyLength - initialVal.length(); 00701 00702 // correct column to point at end of range, taking replacement width diff into account 00703 // 2 == % + { 00704 column -= 2 + keyLength - initialVal.length(); 00705 00706 // always add ${...} to the editable ranges 00707 // only add %{...} to the editable ranges when it's value equals the key 00708 ifDebug(kDebug() << "char is:" << c << "initial value is:" << initialValues[key] << " after fixup is:" << initialVal;) 00709 if (c == '$' || key == initialValues[key]) { 00710 if (!keyQueue.contains(key)) { 00711 keyQueue.append(key); 00712 } 00713 00714 // support for multiline initial val, e.g. selection 00715 int endColumn = column - initialVal.length(); 00716 int endLine = line; 00717 for (int j = 0; j < initialVal.length(); ++j) { 00718 if (initialVal.at(j) == '\n') { 00719 endColumn = 0; 00720 ++endLine; 00721 } else { 00722 ++endColumn; 00723 } 00724 } 00725 Range tmp = Range(line, column - initialVal.length(), endLine, endColumn ); 00726 ifDebug(kDebug() << "range is:" << tmp;) 00727 00728 if (force_first) { 00729 QList<Range> range_list = ranges.values(key); 00730 range_list.append(tmp); 00731 ranges.remove(key); 00732 00733 while (!range_list.isEmpty()) { 00734 ranges.insert(key, range_list.takeLast()); 00735 } 00736 } else { 00737 ranges.insert(key, tmp); 00738 } 00739 00740 mirrorBehaviourBuildHelper.insert(tmp, behaviour); 00741 } 00742 } 00743 00744 startPos = -1; 00745 00746 ifDebug(kDebug() << "i=" << i << " template size=" << templateString.size();) 00747 } else { 00748 ++column; 00749 lastWasBrace = false; 00750 } 00751 } 00752 00753 if (lastWasBrace && (!finalCursorPosition.isValid())) { 00754 templateString += " "; 00755 spacers.append(Range(line, column, line, column + 1)); 00756 ++column; 00757 } 00758 00759 doc()->replaceText(*m_wholeTemplateRange, templateString); 00760 00761 Q_ASSERT(!m_wholeTemplateRange->toRange().isEmpty()); 00762 00763 if (m_view) { 00768 m_view->updateView(true); 00769 } 00770 00771 if (finalCursorPosition.isValid()) { 00772 m_finalCursorPosition = doc()->newMovingCursor(finalCursorPosition); 00773 } else { 00774 m_finalCursorPosition = doc()->newMovingCursor(Cursor(line, column)); 00775 } 00776 00777 if (ranges.isEmpty()) { 00778 return; 00779 } 00780 00781 KateRendererConfig *config = m_view->renderer()->config(); 00782 00783 // TODO: finetune the attribute alpha values, text ranges behave a bit different there... 00784 00785 Attribute::Ptr editableAttribute = getAttribute(config->templateEditablePlaceholderColor(), 255); 00786 editableAttribute->setDynamicAttribute( 00787 Attribute::ActivateCaretIn, getAttribute(config->templateFocusedEditablePlaceholderColor(), 255) 00788 ); 00789 00790 Attribute::Ptr mirroredAttribute = getAttribute(config->templateNotEditablePlaceholderColor(), 255); 00791 00792 m_wholeTemplateRange->setAttribute(getAttribute(config->templateBackgroundColor(), 200)); 00793 00794 // create moving ranges ranges for each found variable 00795 // if the variable exists more than once, create "mirrored" ranges 00796 foreach(const QString& key, keyQueue) { 00797 const QList<Range> &values = ranges.values(key); 00798 // used as parent for mirrored variables, 00799 // and as only item for not-mirrored variables 00800 MovingRange* parent = doc()->newMovingRange(values.last(), MovingRange::ExpandLeft | MovingRange::ExpandRight); 00801 00802 if (values.size() > 1) { 00803 // add all "real" ranges as children 00804 for (int i = 0; i < values.size(); ++i) { 00805 MovingRange* range = doc()->newMovingRange(values[i], MovingRange::ExpandLeft | MovingRange::ExpandRight); 00806 00807 // remember child - parent mapping 00808 m_templateRangesChildren[parent].push_back(range); 00809 m_templateRangesChildToParent[range] = parent; 00810 00811 // the last item will be our real first range (see multimap docs) 00812 if (i == values.size() - 1) { 00813 range->setAttribute(editableAttribute); 00814 m_uneditedRanges.append(range); 00815 m_masterRanges.append(range); 00816 } else { 00817 range->setAttribute(mirroredAttribute); 00818 m_mirrorBehaviour.insert(range, mirrorBehaviourBuildHelper[values[i]]); 00819 } 00820 } 00821 } else { 00822 // just a single range 00823 parent->setAttribute(editableAttribute); 00824 m_uneditedRanges.append(parent); 00825 m_masterRanges.append(parent); 00826 } 00827 00828 m_templateRanges.append(parent); 00829 } 00830 00831 // create spacers, only once create the attribute 00832 Attribute::Ptr attribute(new Attribute()); 00833 attribute->setFont(QFont("fixed", 1)); 00834 attribute->setFontStrikeOut(true); 00835 attribute->setFontOverline(true); 00836 attribute->setFontUnderline(true); 00837 foreach(const Range& spacer, spacers) { 00838 MovingRange *r = doc()->newMovingRange(spacer); 00839 r->setAttribute(attribute); 00840 m_spacersMovingRanges.append(r); 00841 } 00842 00843 qSort(m_masterRanges.begin(), m_masterRanges.end(), cmp_moving_ranges); 00844 00845 setCurrentRange(m_templateRanges.first()); 00846 mirrorBehaviourBuildHelper.clear(); 00847 00848 //OPTIMIZE ME, PERHAPS ONLY DO THE MIRRORING ACTION FOR THE MASTER RANGE IN THE CODE ABOVE 00849 //THIS WOULD REDUCE MIRRORING ACTIONS 00850 00851 m_initialRemodify = true; 00852 foreach(MovingRange* smr, m_masterRanges) { 00853 slotTextChanged(doc(), Range(*smr)); 00854 } 00855 00856 m_initialRemodify = false; 00857 00858 } 00859 00860 void KateTemplateHandler::slotTextChanged(Document* document, const Range& range) 00861 { 00862 Q_ASSERT(document == doc()); 00863 00864 ifDebug(kDebug() << "text changed" << document << range;) 00865 00866 if (m_isMirroring) { 00867 ifDebug(kDebug() << "mirroring - ignoring edit";) 00868 return; 00869 } 00870 00871 if (!m_initialRemodify) { 00872 if ((!m_editWithUndo && doc()->isEditRunning()) || range.isEmpty()) { 00873 ifDebug(kDebug() << "slotTextChanged returning prematurely";) 00874 return; 00875 } 00876 } 00877 00878 if (m_wholeTemplateRange->toRange().isEmpty()) { 00879 ifDebug(kDebug() << "template range got deleted, exiting";) 00880 cleanupAndExit(); 00881 return; 00882 } 00883 00884 if (range.end() == *m_finalCursorPosition) { 00885 ifDebug(kDebug() << "editing at final cursor position, exiting.";) 00886 cleanupAndExit(); 00887 return; 00888 } 00889 00890 if (!m_wholeTemplateRange->toRange().contains(range.start())) { 00891 // outside our template or one of our own changes 00892 ifDebug(kDebug() << range << "not contained in" << m_wholeTemplateRange->toRange();) 00893 cleanupAndExit(); 00894 return; 00895 } 00896 00897 ifDebug(kDebug() << "see if we have to mirror the edit";) 00898 00899 // Check if @p range is mirrored. 00900 // If we have two adjacent mirrored ranges, think ${first}${second}, 00901 // the second will be mirrored, as that's what the user will see "activated", 00902 // since Kate uses contains() that does a comparison "< end()". 00903 // We use "<= end()" though so we can handle contents that were added at the end of a range. 00904 // TODO: make it possible to select either, the left or right rannge (LOWPRIO) 00905 00906 // The found child range to act as base for mirroring. 00907 MovingRange* baseRange = 0; 00908 // The left-adjacent range that gets some special treatment (if it exists). 00909 MovingRange* leftAdjacentRange = 0; 00910 00911 foreach(MovingRange* parent, m_templateRanges) { 00912 if (customContains(parent, range.start())) { 00913 if (m_templateRangesChildren[parent].isEmpty()) { 00914 // simple, not-mirrored range got changed 00915 if (!m_initialRemodify) 00916 m_uneditedRanges.removeOne(parent); 00917 } else { 00918 // handle mirrored ranges 00919 if (baseRange) { 00920 // look for adjacent range (we find the right-handed one here) 00921 ifDebug(kDebug() << "looking for adjacent mirror to" << *baseRange;) 00922 foreach(MovingRange* child, m_templateRangesChildren[parent]) { 00923 if (child->start() == range.start() && child->end() >= range.end()) { 00924 ifDebug(kDebug() << "found adjacent mirror - using as base" << *child;) 00925 leftAdjacentRange = baseRange; 00926 baseRange = child; 00927 break; 00928 } 00929 } 00930 00931 // adjacent mirror handled, we can finish 00932 break; 00933 } else { 00934 // find mirrored range that got edited 00935 foreach(MovingRange* child, m_templateRangesChildren[parent]) { 00936 if (customContains(child, range.start())) { 00937 baseRange = child; 00938 break; 00939 } 00940 } 00941 00942 if (baseRange && baseRange->end() != range.end()) { 00943 // finish, don't look for adjacent mirror as we are not at the end of this range 00944 break; 00945 } else if (baseRange && (baseRange->isEmpty() || range == *baseRange)) { 00946 // always add to empty ranges first. else ${foo}${bar} will make foo unusable when 00947 // it gets selected and an edit takes place. 00948 break; 00949 } // else don't finish, look for baseRange or adjacent mirror first 00950 } 00951 } 00952 00953 } else if (baseRange) { 00954 // no adjacent mirrored range found, we can finish 00955 break; 00956 } 00957 } 00958 00959 if (baseRange) { 00960 m_uneditedRanges.removeOne(baseRange); 00961 00962 if (leftAdjacentRange) { 00963 // revert that something got added to the adjacent range 00964 ifDebug(kDebug() << "removing edited range" << range << "from left adjacent range" << *leftAdjacentRange;) 00965 leftAdjacentRange->setRange(Range(leftAdjacentRange->start(), range.start())); 00966 ifDebug(kDebug() << "new range:" << *leftAdjacentRange;) 00967 } 00968 00969 syncMirroredRanges(baseRange); 00970 00971 if (range.start() == baseRange->start() && m_view) { 00972 // TODO: update the attribute, since kate doesn't do that automatically 00973 // TODO: fix in kate itself 00974 // TODO: attribute->changed == undefined reference... 00975 } 00976 } else { 00977 ifDebug(kDebug() << "no range found to mirror this edit";) 00978 } 00979 } 00980 00981 void KateTemplateHandler::syncMirroredRanges(MovingRange* range) 00982 { 00983 Q_ASSERT(m_templateRanges.contains(m_templateRangesChildToParent[range])); 00984 00985 m_isMirroring = true; 00986 doc()->editStart(); 00987 00988 const QString &newText = doc()->text(*range); 00989 ifDebug(kDebug() << "mirroring" << newText << "from" << *range;) 00990 00991 foreach(MovingRange* sibling, m_templateRangesChildren[m_templateRangesChildToParent[range]]) { 00992 if (sibling != range) { 00993 doc()->replaceText(*sibling, m_mirrorBehaviour[sibling].getMirrorString(newText)); 00994 } 00995 } 00996 00999 doc()->editEnd(); 01000 m_isMirroring = false; 01001 } 01002 01003 01004 KateView* KateTemplateHandler::view() 01005 { 01006 return qobject_cast<KateView*>(m_view); 01007 } 01008 01009 01010 //BEGIN MIRROR BEHAVIOUR 01011 KateTemplateHandler::MirrorBehaviour::MirrorBehaviour() 01012 : m_behaviour(Clone) 01013 { 01014 } 01015 01016 KateTemplateHandler::MirrorBehaviour::MirrorBehaviour(const QString ®exp, 01017 const QString &replacement, 01018 const QString& flags) 01019 : m_behaviour(Regexp) 01020 , m_search(regexp) 01021 , m_replace(replacement) 01022 { 01023 m_global = flags.contains("g"); 01024 m_expr = QRegExp(regexp, flags.contains("i") ? Qt::CaseInsensitive : Qt::CaseSensitive, QRegExp::RegExp2); 01025 } 01026 01027 KateTemplateHandler::MirrorBehaviour::MirrorBehaviour(KateTemplateScript* templateScript, 01028 const QString& functionName, 01029 KateTemplateHandler* handler) 01030 : m_behaviour(Scripted) 01031 , m_templateScript(templateScript) 01032 , m_functionName(functionName) 01033 , m_handler(handler) 01034 { 01035 } 01036 01037 KateTemplateHandler::MirrorBehaviour::~MirrorBehaviour() 01038 { 01039 } 01040 01041 01042 QString KateTemplateHandler::MirrorBehaviour::getMirrorString(const QString &source) 01043 { 01044 QString ahead; 01045 QString output; 01046 QString finalOutput; 01047 int pos; 01048 int matchCounter = 0; 01049 01050 switch (m_behaviour) { 01051 01052 case Clone: 01053 return source; 01054 break; 01055 01056 case Regexp: { 01057 ifDebug(kDebug() << "regexp " << m_search << " replacement " << m_replace;) 01058 01059 if (m_global) { 01060 ahead = source; 01061 01062 while (ahead.length() > 0) { 01063 if ((pos = m_expr.indexIn(ahead)) == -1) { 01064 return finalOutput + ahead; 01065 } 01066 01067 QStringList results = m_expr.capturedTexts(); 01068 output = KateRegExpSearch::buildReplacement(m_replace, results, ++matchCounter); 01069 01070 finalOutput = finalOutput + ahead.left(pos) + output; 01071 ahead = ahead.mid(pos + m_expr.matchedLength()); 01072 } 01073 01074 return finalOutput; 01075 } else { 01076 if ((pos = m_expr.indexIn(source)) == -1) { 01077 return source; 01078 } 01079 01080 QStringList results = m_expr.capturedTexts(); 01081 output = KateRegExpSearch::buildReplacement(m_replace, results, 1); 01082 return source.left(pos) + output + source.mid(pos + m_expr.matchedLength()); 01083 } 01084 01085 break; 01086 } 01087 01088 case Scripted: { 01089 KateTemplateScript *script = KateGlobal::self()->scriptManager()->templateScript(m_templateScript); 01090 01091 if (script) { 01092 QString result = script->invoke(m_handler->view(), m_functionName, source); 01093 01094 if (!result.isNull()) { 01095 return result; 01096 } 01097 } 01098 01099 return source; 01100 } 01101 01102 default: 01103 return QString(); 01104 } 01105 01106 return QString(); 01107 } 01108 01109 //END MIRROR BEHAVOUR 01110 01111 01112 #include "katetemplatehandler.moc" 01113 01114 // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; replace-tabs on; replace-tabs on; replace-tabs on;
KDE 4.6 API Reference