• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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 &regexp,
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;

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal