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

Kate

  • kde-4.14
  • applications
  • kate
  • part
  • utils
katetemplatehandler.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries and the Kate part.
2  *
3  * Copyright (C) 2004,2010 Joseph Wenninger <jowenn@kde.org>
4  * Copyright (C) 2009 Milian Wolff <mail@milianw.de>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB. If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include <QtCore/QQueue>
23 
24 #include <ktexteditor/movingcursor.h>
25 #include <ktexteditor/movingrange.h>
26 
27 #include "katetemplatehandler.h"
28 #include "katedocument.h"
29 #include "kateview.h"
30 #include "kateconfig.h"
31 #include "katerenderer.h"
32 #include "kateundomanager.h"
33 #include "kateregexpsearch.h"
34 #include "kateglobal.h"
35 #include "script/katetemplatescript.h"
36 #include "script/katescriptmanager.h"
37 
38 using namespace KTextEditor;
39 
40 #define ifDebug(x)
41 
42 static bool cmp_moving_ranges(const KTextEditor::MovingRange* r1, const KTextEditor::MovingRange* r2)
43 {
44  return r1->start() < r2->start();
45 }
46 
48 static bool customContains(MovingRange* range, const Cursor& cursor)
49 {
50  return range->start() <= cursor && range->end() >= cursor;
51 }
52 
53 static bool customContains(const KTextEditor::Range &range, const Cursor& cursor)
54 {
55  return range.start() <= cursor && range.end() >= cursor;
56 }
57 
58 /* ####################################### */
59 
60 KateTemplateHandler::KateTemplateHandler(KateView *view,
61  const Cursor& position,
62  const QString &templateString,
63  const QMap<QString, QString> &initialValues,
64  KateUndoManager* undoManager,
65  KateTemplateScript* templateScript)
66  : QObject(view)
67  , m_view(view)
68  , m_undoManager(undoManager)
69  , m_wholeTemplateRange(0)
70  , m_finalCursorPosition(0)
71  , m_lastCaretPosition(position)
72  , m_isMirroring(false)
73  , m_editWithUndo(false)
74  , m_jumping(false)
75  , m_templateScript(templateScript)
76 {
80  Q_ASSERT (m_view);
81 
82  ifDebug(kDebug() << templateString << initialValues;)
83 
84  QMap<QString, QString> initial_Values(initialValues);
85 
86  if (initial_Values.contains("selection")) {
87  if (initial_Values["selection"].isEmpty()) {
88  initial_Values[ "selection" ] = m_view->selectionText();
89  }
90  }
91 
92  if (m_view->selection()) {
93  m_lastCaretPosition = m_view->selectionRange().start();
94  m_view->removeSelectionText();
95  }
96 
97  ifDebug(kDebug() << initial_Values;)
98 
99  connect(doc(), SIGNAL(aboutToReload(KTextEditor::Document*)),
100  this, SLOT(cleanupAndExit()));
101 
102  connect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
103  this, SLOT(slotTemplateInserted(KTextEditor::Document*,KTextEditor::Range)));
104 
106 
107  // we want one undo action >= START
108  doc()->setUndoMergeAllEdits(true);
109  doc()->editStart();
110 
114  if (!doc()->insertText(m_lastCaretPosition, templateString)) {
115  doc()->editEnd();
116  doc()->setUndoMergeAllEdits(false);
117  cleanupAndExit();
118  }
119 
123  Q_ASSERT(m_wholeTemplateRange);
124 
125  // end edit here, we need correct syntax highlighting in align!
126  doc()->editEnd();
127 
128  // indent the inserted template properly, this makes it possible
129  // to share snippets e.g. via GHNS without caring about
130  // what indent-style to use.
131  doc()->align(m_view, *m_wholeTemplateRange);
132 
138 
139  doc()->editStart();
140  handleTemplateString(initial_Values);
141  m_undoManager->undoSafePoint();
142  doc()->editEnd();
143  doc()->setUndoMergeAllEdits(false);
144 
145  if (!initialValues.isEmpty()) {
146  // only do complex stuff when required
147  if (!m_templateRanges.isEmpty()) {
148  foreach(View* view, doc()->views()) {
149  setupEventHandler(view);
150  }
151 
152  connect(doc(), SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)),
153  this, SLOT(slotViewCreated(KTextEditor::Document*,KTextEditor::View*)));
154  connect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
155  this, SLOT(slotTextChanged(KTextEditor::Document*,KTextEditor::Range)));
156  connect(doc(), SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range)),
157  this, SLOT(slotTextChanged(KTextEditor::Document*,KTextEditor::Range)));
158 
159  setEditWithUndo(undoManager->isActive());
160 
161  connect(undoManager, SIGNAL(isActiveChanged(bool)),
162  this, SLOT(setEditWithUndo(bool)));
163 
164  } else {
165  // when no interesting ranges got added, we can terminate directly
166  jumpToFinalCursorPosition();
167  cleanupAndExit();
168  }
169 
170  } else {
171  cleanupAndExit();
172  }
173 }
174 
175 KateTemplateHandler::~KateTemplateHandler()
176 {
177 }
178 
179 void KateTemplateHandler::slotTemplateInserted(Document *document, const Range& range)
180 {
181  Q_ASSERT(document == doc());
182  Q_UNUSED(document);
183  ifDebug(kDebug() << "template range inserted" << range;)
184 
185  m_wholeTemplateRange = doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
186 
187  disconnect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
188  this, SLOT(slotTemplateInserted(KTextEditor::Document*,KTextEditor::Range)));
189 }
190 
191 void KateTemplateHandler::cleanupAndExit()
192 {
193  ifDebug(kDebug() << "cleaning up and exiting";)
194  disconnect(doc(), SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)),
195  this, SLOT(slotViewCreated(KTextEditor::Document*,KTextEditor::View*)));
196  disconnect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
197  this, SLOT(slotTextChanged(KTextEditor::Document*,KTextEditor::Range)));
198  disconnect(doc(), SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range)),
199  this, SLOT(slotTextChanged(KTextEditor::Document*,KTextEditor::Range)));
200 
201  if (!m_templateRanges.isEmpty()) {
202  foreach(MovingRange* range, m_templateRanges) {
203  // delete all children
204  foreach(MovingRange* child, m_templateRangesChildren[range])
205  delete child;
206 
207  delete range;
208  }
209 
210  m_templateRanges.clear();
211  m_templateRangesChildren.clear();
212  m_templateRangesChildToParent.clear();
213  }
214 
215  // no children if no ranges around
216  Q_ASSERT(m_templateRangesChildren.isEmpty());
217  Q_ASSERT(m_templateRangesChildToParent.isEmpty());
218 
219  if (!m_spacersMovingRanges.isEmpty()) {
220  foreach(MovingRange* range, m_spacersMovingRanges) {
221  doc()->removeText(*range);
222  delete range;
223  }
224 
225  m_spacersMovingRanges.clear();
226  }
227 
228  delete m_wholeTemplateRange;
229  delete m_finalCursorPosition;
230  delete this;
231 }
232 
233 void KateTemplateHandler::jumpToFinalCursorPosition()
234 {
235  if (!m_wholeTemplateRange || customContains(m_wholeTemplateRange->toRange(), m_view->cursorPosition())) {
236  m_view->setSelection(Range::invalid());
237  m_view->setCursorPosition(*m_finalCursorPosition);
238  }
239 }
240 
241 KateDocument *KateTemplateHandler::doc()
242 {
243  return m_view->doc();
244 }
245 
246 void KateTemplateHandler::setEditWithUndo(const bool &enabled)
247 {
248  m_editWithUndo = enabled;
249 }
250 
251 void KateTemplateHandler::slotViewCreated(Document* document, View* view)
252 {
253  Q_ASSERT(document == doc());
254  Q_UNUSED(document)
255  setupEventHandler(view);
256 }
257 
258 void KateTemplateHandler::setupEventHandler(View* view)
259 {
260  view->focusProxy()->installEventFilter(this);
261 }
262 
263 bool KateTemplateHandler::eventFilter(QObject* object, QEvent* event)
264 {
265  // prevent indenting by eating the keypress event for TAB
266  if (event->type() == QEvent::KeyPress) {
267  QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
268 
269  if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) {
270  if (!m_view->isCompletionActive()) {
271  return true;
272  }
273  }
274  }
275 
276  // actually offer shortcuts for navigation
277 
278  if (event->type() == QEvent::ShortcutOverride) {
279  QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
280 
281  if (keyEvent->key() == Qt::Key_Return && keyEvent->modifiers() & Qt::AltModifier) {
282  // terminate
283  jumpToFinalCursorPosition();
284  cleanupAndExit();
285  keyEvent->accept();
286  return true;
287 
288  } else if (keyEvent->key() == Qt::Key_Escape) {
289  if (!m_view->selection()) {
290  // terminate
291  jumpToFinalCursorPosition();
292  cleanupAndExit();
293  keyEvent->accept();
294  return true;
295  }
296 
297  } else if (keyEvent->key() == Qt::Key_Tab && !m_view->isCompletionActive()) {
298  if (keyEvent->modifiers() & Qt::Key_Shift) {
299  jumpToPreviousRange();
300 
301  } else {
302  jumpToNextRange();
303  }
304 
305  keyEvent->accept();
306  return true;
307 
308  } else if (keyEvent->key() == Qt::Key_Backtab && !m_view->isCompletionActive()) {
309  jumpToPreviousRange();
310  keyEvent->accept();
311  return true;
312  }
313  }
314 
315  return QObject::eventFilter(object, event);
316 }
317 
318 void KateTemplateHandler::jumpToPreviousRange()
319 {
320  const Cursor & cursor = m_view->cursorPosition();
321 
322  if (cursor == *m_finalCursorPosition) {
323  // wrap and jump to last range
324  setCurrentRange(m_masterRanges.last());
325  return;
326  }
327 
328  MovingRange* previousRange = 0;
329 
330  foreach(MovingRange* range, m_masterRanges) {
331  if (range->start() >= cursor) {
332  continue;
333  }
334 
335  if (!previousRange || range->start() > previousRange->start()) {
336  previousRange = range;
337 
338  if (m_templateRangesChildToParent.value(previousRange)) previousRange = m_templateRangesChildToParent.value(previousRange);
339  }
340  }
341 
342  if (previousRange) {
343  setCurrentRange(previousRange);
344 
345  } else {
346  // wrap and jump to final cursor
347  jumpToFinalCursorPosition();
348  }
349 }
350 
351 void KateTemplateHandler::jumpToNextRange()
352 {
353  const Cursor & cursor = m_view->cursorPosition();
354 
355  if (cursor == *m_finalCursorPosition) {
356  // wrap and jump to first range
357  setCurrentRange(m_masterRanges.first());
358  return;
359  }
360 
361  MovingRange* nextRange = 0;
362 
363  foreach(MovingRange* range, m_masterRanges) {
364  if (range->start() <= cursor) {
365  continue;
366  }
367 
368  if (!nextRange || range->start() < nextRange->start()) {
369  nextRange = range;
370  }
371  }
372 
373  if (nextRange) {
374  if (m_templateRangesChildToParent.value(nextRange)) nextRange = m_templateRangesChildToParent.value(nextRange);
375 
376  setCurrentRange(nextRange);
377 
378  } else {
379  // wrap and jump to final cursor
380  jumpToFinalCursorPosition();
381  }
382 }
383 
384 void KateTemplateHandler::setCurrentRange(MovingRange* range)
385 {
386  if (!m_templateRangesChildren[range].isEmpty()) {
387  ifDebug(kDebug() << "looking for mirroring range";)
388  // jump to first mirrored range
389  bool found = false;
390  foreach(MovingRange* childRange, m_templateRangesChildren[range]) {
391  ifDebug(kDebug() << "checking range equality";)
392 
393  if (m_masterRanges.contains(childRange)) {
394  ifDebug(kDebug() << "found master range";)
395  range = childRange;
396  found = true;
397  break;
398  }
399  }
400 
401  if (!found) {
402  range = m_templateRangesChildren[range].first();
403  }
404  }
405 
406  m_jumping = true;
407 
408  if (m_uneditedRanges.contains(range))
409  m_view->setSelection(*range);
410 
411  m_view->setCursorPosition(range->start());
412 
413  m_jumping = false;
414 
415  m_lastCaretPosition = range->start();
416 }
417 
421 Attribute::Ptr getAttribute(QColor color, int alpha = 230)
422 {
423  Attribute::Ptr attribute(new Attribute());
424  color.setAlpha(alpha);
425  attribute->setBackground(QBrush(color));
426  return attribute;
427 }
428 
429 void KateTemplateHandler::handleTemplateString(const QMap< QString, QString >& initialValues)
430 {
431  QString templateString = doc()->text(*m_wholeTemplateRange);
432 
433  int line = m_wholeTemplateRange->start().line();
434  int column = m_wholeTemplateRange->start().column();
435 
436  // not equal -1 when we found a start position
437  int startPos = -1;
438 
439  bool lastWasBrace = false;
440 
441  // each found variable gets its range(s) added to the list.
442  // the key is the varname, e.g. the same as in initialValues
443  // to be able to iterate over them in a FIFO matter, also store
444  // the keys in a queue.
445  QQueue<QString> keyQueue;
446  QMultiMap<QString, Range> ranges;
447  QMap<Range, MirrorBehaviour> mirrorBehaviourBuildHelper;
448 
449 
450  QList<Range> spacers;
451  // valid, if we find an occurrence of ${cursor}
452  Cursor finalCursorPosition = Cursor::invalid();
453 
454  // parse string for ${VAR} or %{VAR}
455  // VAR must not contain $ or %
456  // VAR must not contain newlines
457  // VAR must be set as key in initialValues
458  // expression must not be escaped
459 
460  for (int i = 0; i < templateString.size(); ++i) {
461  ifDebug(kDebug() << "checking character:" << templateString[i];)
462 
463  if (templateString[i] == '\n') {
464  lastWasBrace = false;
465  ++line;
466  column = 0;
467 
468  if (startPos != -1) {
469  // don't allow variables to span multiple lines
470  startPos = -1;
471  }
472 
473  } else if ((templateString[i] == '%' || templateString[i] == '$')
474  && i + 1 < templateString.size() && templateString[i+1] == '{') {
475 
476  // check whether this var is escaped
477  int escapeChars = 0;
478 
479  while (i - escapeChars > 0 && templateString[i - escapeChars - 1] == '\\') {
480  ++escapeChars;
481  }
482 
483  if (escapeChars > 0) {
484  ifDebug(kDebug() << "found" << escapeChars << "escape chars at " << templateString.mid(i - escapeChars - 10, escapeChars + 10);)
485  // remove half of the escape chars (i.e. \\ => \) and make sure the
486  // odd rest is removed as well (i.e. the one that escapes this var)
487  int toRemove = (escapeChars + 1) / 2;
488  ifDebug(kDebug() << "will remove" << toRemove << "of those escape chars";)
489  templateString.remove(i - escapeChars, toRemove);
490  i -= toRemove;
491  column -= toRemove;
492  }
493 
494  if (escapeChars % 2 == 0) {
495  // don't check for startPos == -1 here, overwrite blindly since nested variables are not supported
496  if (lastWasBrace) {
497  templateString.insert(i, " ");
498  spacers.append(Range(line, column, line, column + 1));
499  ++i;
500  ++column;
501  }
502 
503  startPos = i;
504  }
505 
506  lastWasBrace = false;
507 
508  // skip '{'
509  ++i;
510  column += 2;
511 
512  } else if ((startPos != -1) && (templateString[i] == ':')) { // skip init value, handled by KTE
513  i++;
514  column++;
515  int backslash_count = 0;
516 
517  for (;i < templateString.size();i++, column++) {
518  if (templateString[i] == '\n') {
519  ++line;
520  column = 0;
521 
522  if (startPos != -1) {
523  // don't allow variables to span multiple lines
524  startPos = -1;
525  }
526 
527  break;
528  }
529 
530  if (templateString[i] == '}') {
531  if ((backslash_count % 2) == 0) {
532  i--;
533  //column--;
534  break;
535 
536  } else {
537  backslash_count = 0;
538  }
539 
540  } else if (templateString[i] == '\\') {
541  backslash_count++;
542 
543  } else { // any character teminates a backslash sequence
544  backslash_count = 0;
545  }
546  }
547 
548  } else if ((startPos != -1) && (templateString[i] == '/')) { // skip regexp
549  i++;
550  column++;
551  int backslash_count = 0;
552  int slashcount = 1;
553 
554  for (;i < templateString.size();i++, column++) {
555  if (templateString[i] == '\n') {
556  ++line;
557  column = 0;
558 
559  if (startPos != -1) {
560  // don't allow variables to span multiple lines
561  startPos = -1;
562  }
563 
564  break;
565  }
566 
567  if (templateString[i] == '/') {
568  if ((backslash_count % 2) == 0)
569  slashcount++;
570 
571  backslash_count = 0;
572 
573  } else if (templateString[i] == '\\') {
574  backslash_count++;
575 
576  } else { // any character teminates a backslash sequence
577  backslash_count = 0;
578  }
579 
580  if (slashcount == 3) {
581  column++;
582  break;
583  }
584  }
585 
586  } else if (templateString[i] == '}' && startPos != -1) {
587  lastWasBrace = true;
588  bool force_first = false;
589  // get key, i.e. contents between ${..}
590  QString key = templateString.mid(startPos + 2, i - (startPos + 2));
591  int keyLength = key.length();
592  QString searchReplace;
593  ifDebug(kDebug() << "key found:" << key;)
594  bool check_slash = false;
595  bool check_colon = false;
596  bool check_backtick = false;
597  int pos_slash = key.indexOf("/");
598  int pos_colon = key.indexOf(":");
599  int pos_backtick = key.indexOf("`");
600 
601  if ((pos_slash == -1) && (pos_colon == -1)) {
602  // do nothing
603  } else if ((pos_slash != -1) && (pos_colon == -1)) {
604  check_slash = true;
605  } else if ((pos_slash == -1) && (pos_colon != -1)) {
606  check_colon = true;
607  } else {
608  if (pos_colon < pos_slash) {
609  check_colon = true;
610  } else {
611  check_slash = true;
612  }
613  }
614 
615  if (!check_slash && !check_colon && pos_backtick >= 0) {
616  check_backtick = true;
617  }
618 
619  QString functionName;
620 
621  if (check_slash) {
622  searchReplace = key.mid(pos_slash + 1);
623  key = key.left(pos_slash);
624  ifDebug(kDebug() << "search_replace" << searchReplace;)
625  } else if (check_colon) {
626  key = key.left(pos_colon);
627  ifDebug(kDebug() << "real key found:" << key;)
628  } else if (check_backtick) {
629  functionName = key.mid(pos_backtick + 1);
630  functionName = functionName.left(functionName.indexOf("`"));
631  key = key.left(pos_backtick);
632  ifDebug(kDebug() << "real key found:" << key << "function:" << functionName;)
633  }
634 
635  if (key.contains("@")) {
636  key = key.left(key.indexOf("@"));
637  force_first = true;
638  }
639 
640  ifDebug(kDebug() << "real key found:" << key;)
641 
642  if (!initialValues.contains(key)) {
643  kWarning() << "unknown variable key:" << key;
644  } else if (key == "cursor") {
645  finalCursorPosition = Cursor(line, column - keyLength - 2);
646  // don't insert anything, just remove the placeholder
647  templateString.remove(startPos, i - startPos + 1);
648  // correct iterator pos, 3 == $ + { + }
649  i -= 3 + keyLength;
650  column -= 2 + keyLength;
651  startPos = -1;
652  } else {
653  MirrorBehaviour behaviour;
654 
655  if (!searchReplace.isEmpty()) {
656  QString search;
657  bool searchValid = false;
658  QString replace;
659  bool replaceValid = false;
660  QString flags;
661  //search part;
662 
663  while (!searchReplace.isEmpty()) {
664  ifDebug(kDebug() << "searchReplace=" << searchReplace;)
665  int regescapes = 0;
666  int pos = searchReplace.indexOf("/");
667 
668  for (int epos = pos - 1; epos >= 0 && searchReplace.at(epos) == '\\'; epos--) {
669  regescapes++;
670  }
671 
672  ifDebug(kDebug() << "regescapes=" << regescapes;)
673  if ((regescapes % 2) == 1) {
674  search += searchReplace.left(pos + 1);
675  searchReplace = searchReplace.mid(pos + 1);
676  ifDebug(kDebug() << "intermediate search string is=" << search;)
677  } else {
678  search += searchReplace.left(pos);
679  searchReplace = searchReplace.mid(pos + 1);
680  searchValid = true;
681  ifDebug(kDebug() << "final search string is=" << search;)
682  ifDebug(kDebug() << "remaining characters in searchReplace" << searchReplace;)
683  break;
684  }
685  }
686 
687  //replace part
688 
689  if (searchValid) {
690  int last_slash = searchReplace.lastIndexOf("/");
691 
692  if (last_slash != -1) {
693  replace = searchReplace.left(last_slash);
694  replaceValid = true;
695  flags = searchReplace.mid(last_slash + 1);
696  }
697  }
698 
699  if (searchValid && replaceValid) {
700  behaviour = MirrorBehaviour(search, replace, flags);
701  }
702 
703  } else if (!functionName.isEmpty()) {
704  behaviour = MirrorBehaviour(m_templateScript, functionName, this);
705  }
706 
707  const QString initialVal = behaviour.getMirrorString(initialValues[key]);
708 
709  // whether the variable starts with % or $
710  QChar c = templateString[startPos];
711 
712  // replace variable with initial value
713  templateString.replace(startPos, i - startPos + 1, initialVal);
714 
715  // correct iterator pos, 3 == % + { + }
716  i -= 3 + keyLength - initialVal.length();
717 
718  // correct column to point at end of range, taking replacement width diff into account
719  // 2 == % + {
720  column -= 2 + keyLength - initialVal.length();
721 
722  // always add ${...} to the editable ranges
723  // only add %{...} to the editable ranges when its value equals the key
724  ifDebug(kDebug() << "char is:" << c << "initial value is:" << initialValues[key] << " after fixup is:" << initialVal;)
725  if (c == '$' || key == initialValues[key]) {
726  if (!keyQueue.contains(key)) {
727  keyQueue.append(key);
728  }
729 
730  // support for multiline initial val, e.g. selection
731  int endColumn = column - initialVal.length();
732  int endLine = line;
733  for (int j = 0; j < initialVal.length(); ++j) {
734  if (initialVal.at(j) == '\n') {
735  endColumn = 0;
736  ++endLine;
737  } else {
738  ++endColumn;
739  }
740  }
741  Range tmp = Range(line, column - initialVal.length(), endLine, endColumn );
742  ifDebug(kDebug() << "range is:" << tmp;)
743 
744  if (force_first) {
745  QList<Range> range_list = ranges.values(key);
746  range_list.append(tmp);
747  ranges.remove(key);
748 
749  while (!range_list.isEmpty()) {
750  ranges.insert(key, range_list.takeLast());
751  }
752  } else {
753  ranges.insert(key, tmp);
754  }
755 
756  mirrorBehaviourBuildHelper.insert(tmp, behaviour);
757  }
758  }
759 
760  startPos = -1;
761 
762  ifDebug(kDebug() << "i=" << i << " template size=" << templateString.size();)
763  } else {
764  ++column;
765  lastWasBrace = false;
766  }
767  }
768 
769  if (lastWasBrace && (!finalCursorPosition.isValid())) {
770  templateString += " ";
771  spacers.append(Range(line, column, line, column + 1));
772  ++column;
773  }
774 
775  doc()->replaceText(*m_wholeTemplateRange, templateString);
776 
777  Q_ASSERT(!m_wholeTemplateRange->toRange().isEmpty());
778 
783  m_view->updateView(true);
784 
785  if (finalCursorPosition.isValid()) {
786  m_finalCursorPosition = doc()->newMovingCursor(finalCursorPosition);
787  } else {
788  m_finalCursorPosition = doc()->newMovingCursor(Cursor(line, column));
789  }
790 
791  if (ranges.isEmpty()) {
792  return;
793  }
794 
795  KateRendererConfig *config = m_view->renderer()->config();
796 
797  // TODO: finetune the attribute alpha values, text ranges behave a bit different there...
798 
799  Attribute::Ptr editableAttribute = getAttribute(config->templateEditablePlaceholderColor(), 255);
800  editableAttribute->setDynamicAttribute(
801  Attribute::ActivateCaretIn, getAttribute(config->templateFocusedEditablePlaceholderColor(), 255)
802  );
803 
804  Attribute::Ptr mirroredAttribute = getAttribute(config->templateNotEditablePlaceholderColor(), 255);
805 
806  m_wholeTemplateRange->setAttribute(getAttribute(config->templateBackgroundColor(), 200));
807 
808  // create moving ranges ranges for each found variable
809  // if the variable exists more than once, create "mirrored" ranges
810  foreach(const QString& key, keyQueue) {
811  const QList<Range> &values = ranges.values(key);
812  // used as parent for mirrored variables,
813  // and as only item for not-mirrored variables
814  MovingRange* parent = doc()->newMovingRange(values.last(), MovingRange::ExpandLeft | MovingRange::ExpandRight);
815 
816  if (values.size() > 1) {
817  // add all "real" ranges as children
818  for (int i = 0; i < values.size(); ++i) {
819  MovingRange* range = doc()->newMovingRange(values[i], MovingRange::ExpandLeft | MovingRange::ExpandRight);
820 
821  // remember child - parent mapping
822  m_templateRangesChildren[parent].push_back(range);
823  m_templateRangesChildToParent[range] = parent;
824 
825  // the last item will be our real first range (see multimap docs)
826  if (i == values.size() - 1) {
827  range->setAttribute(editableAttribute);
828  m_uneditedRanges.append(range);
829  m_masterRanges.append(range);
830  } else {
831  range->setAttribute(mirroredAttribute);
832  m_mirrorBehaviour.insert(range, mirrorBehaviourBuildHelper[values[i]]);
833  }
834  }
835  } else {
836  // just a single range
837  parent->setAttribute(editableAttribute);
838  m_uneditedRanges.append(parent);
839  m_masterRanges.append(parent);
840  }
841 
842  m_templateRanges.append(parent);
843  }
844 
845  // create spacers, only once create the attribute
846  Attribute::Ptr attribute(new Attribute());
847  attribute->setFont(QFont("fixed", 1));
848  attribute->setFontStrikeOut(true);
849  attribute->setFontOverline(true);
850  attribute->setFontUnderline(true);
851  foreach(const Range& spacer, spacers) {
852  MovingRange *r = doc()->newMovingRange(spacer);
853  r->setAttribute(attribute);
854  m_spacersMovingRanges.append(r);
855  }
856 
857  qSort(m_masterRanges.begin(), m_masterRanges.end(), cmp_moving_ranges);
858 
859  setCurrentRange(m_templateRanges.first());
860  mirrorBehaviourBuildHelper.clear();
861 
862  //OPTIMIZE ME, PERHAPS ONLY DO THE MIRRORING ACTION FOR THE MASTER RANGE IN THE CODE ABOVE
863  //THIS WOULD REDUCE MIRRORING ACTIONS
864 
865  m_initialRemodify = true;
866  foreach(MovingRange* smr, m_masterRanges) {
867  slotTextChanged(doc(), Range(*smr));
868  }
869 
870  m_initialRemodify = false;
871 
872 }
873 
874 void KateTemplateHandler::slotTextChanged(Document* document, const Range& range)
875 {
876  Q_ASSERT(document == doc());
877 
878  ifDebug(kDebug() << "text changed" << document << range;)
879 
880  if (m_isMirroring) {
881  ifDebug(kDebug() << "mirroring - ignoring edit";)
882  return;
883  }
884 
885  if (!m_initialRemodify) {
886  if ((!m_editWithUndo && doc()->isEditRunning()) || range.isEmpty()) {
887  ifDebug(kDebug() << "slotTextChanged returning prematurely";)
888  return;
889  }
890  }
891 
892  if (m_wholeTemplateRange->toRange().isEmpty()) {
893  ifDebug(kDebug() << "template range got deleted, exiting";)
894  cleanupAndExit();
895  return;
896  }
897 
898  if (range.end() == *m_finalCursorPosition) {
899  ifDebug(kDebug() << "editing at final cursor position, exiting.";)
900  cleanupAndExit();
901  return;
902  }
903 
904  if (!m_wholeTemplateRange->toRange().contains(range.start())) {
905  // outside our template or one of our own changes
906  ifDebug(kDebug() << range << "not contained in" << m_wholeTemplateRange->toRange();)
907  cleanupAndExit();
908  return;
909  }
910 
911  ifDebug(kDebug() << "see if we have to mirror the edit";)
912 
913  // Check if @p range is mirrored.
914  // If we have two adjacent mirrored ranges, think ${first}${second},
915  // the second will be mirrored, as that's what the user will see "activated",
916  // since Kate uses contains() that does a comparison "< end()".
917  // We use "<= end()" though so we can handle contents that were added at the end of a range.
918  // TODO: make it possible to select either, the left or right rannge (LOWPRIO)
919 
920  // The found child range to act as base for mirroring.
921  MovingRange* baseRange = 0;
922  // The left-adjacent range that gets some special treatment (if it exists).
923  MovingRange* leftAdjacentRange = 0;
924 
925  foreach(MovingRange* parent, m_templateRanges) {
926  if (customContains(parent, range.start())) {
927  if (m_templateRangesChildren[parent].isEmpty()) {
928  // simple, not-mirrored range got changed
929  if (!m_initialRemodify)
930  m_uneditedRanges.removeOne(parent);
931  } else {
932  // handle mirrored ranges
933  if (baseRange) {
934  // look for adjacent range (we find the right-handed one here)
935  ifDebug(kDebug() << "looking for adjacent mirror to" << *baseRange;)
936  foreach(MovingRange* child, m_templateRangesChildren[parent]) {
937  if (child->start() == range.start() && child->end() >= range.end()) {
938  ifDebug(kDebug() << "found adjacent mirror - using as base" << *child;)
939  leftAdjacentRange = baseRange;
940  baseRange = child;
941  break;
942  }
943  }
944 
945  // adjacent mirror handled, we can finish
946  break;
947  } else {
948  // find mirrored range that got edited
949  foreach(MovingRange* child, m_templateRangesChildren[parent]) {
950  if (customContains(child, range.start())) {
951  baseRange = child;
952  break;
953  }
954  }
955 
956  if (baseRange && baseRange->end() != range.end()) {
957  // finish, don't look for adjacent mirror as we are not at the end of this range
958  break;
959  } else if (baseRange && (baseRange->isEmpty() || range == *baseRange)) {
960  // always add to empty ranges first. else ${foo}${bar} will make foo unusable when
961  // it gets selected and an edit takes place.
962  break;
963  } // else don't finish, look for baseRange or adjacent mirror first
964  }
965  }
966 
967  } else if (baseRange) {
968  // no adjacent mirrored range found, we can finish
969  break;
970  }
971  }
972 
973  if (baseRange) {
974  m_uneditedRanges.removeOne(baseRange);
975 
976  if (leftAdjacentRange) {
977  // revert that something got added to the adjacent range
978  ifDebug(kDebug() << "removing edited range" << range << "from left adjacent range" << *leftAdjacentRange;)
979  leftAdjacentRange->setRange(Range(leftAdjacentRange->start(), range.start()));
980  ifDebug(kDebug() << "new range:" << *leftAdjacentRange;)
981  }
982 
983  syncMirroredRanges(baseRange);
984 
985  if (range.start() == baseRange->start()) {
986  // TODO: update the attribute, since kate doesn't do that automatically
987  // TODO: fix in kate itself
988  // TODO: attribute->changed == undefined reference...
989  }
990  } else {
991  ifDebug(kDebug() << "no range found to mirror this edit";)
992  }
993 }
994 
995 void KateTemplateHandler::syncMirroredRanges(MovingRange* range)
996 {
997  Q_ASSERT(m_templateRanges.contains(m_templateRangesChildToParent[range]));
998 
999  m_isMirroring = true;
1000  doc()->editStart();
1001 
1002  const QString &newText = doc()->text(*range);
1003  ifDebug(kDebug() << "mirroring" << newText << "from" << *range;)
1004 
1005  foreach(MovingRange* sibling, m_templateRangesChildren[m_templateRangesChildToParent[range]]) {
1006  if (sibling != range) {
1007  doc()->replaceText(*sibling, m_mirrorBehaviour[sibling].getMirrorString(newText));
1008  }
1009  }
1010 
1013  doc()->editEnd();
1014  m_isMirroring = false;
1015 }
1016 
1017 
1018 KateView* KateTemplateHandler::view()
1019 {
1020  return m_view;
1021 }
1022 
1023 
1024 //BEGIN MIRROR BEHAVIOUR
1025 KateTemplateHandler::MirrorBehaviour::MirrorBehaviour()
1026  : m_behaviour(Clone)
1027 {
1028 }
1029 
1030 KateTemplateHandler::MirrorBehaviour::MirrorBehaviour(const QString &regexp,
1031  const QString &replacement,
1032  const QString& flags)
1033  : m_behaviour(Regexp)
1034  , m_search(regexp)
1035  , m_replace(replacement)
1036 {
1037  m_global = flags.contains("g");
1038  m_expr = QRegExp(regexp, flags.contains("i") ? Qt::CaseInsensitive : Qt::CaseSensitive, QRegExp::RegExp2);
1039 }
1040 
1041 KateTemplateHandler::MirrorBehaviour::MirrorBehaviour(KateTemplateScript* templateScript,
1042  const QString& functionName,
1043  KateTemplateHandler* handler)
1044  : m_behaviour(Scripted)
1045  , m_templateScript(templateScript)
1046  , m_functionName(functionName)
1047  , m_handler(handler)
1048 {
1049 }
1050 
1051 KateTemplateHandler::MirrorBehaviour::~MirrorBehaviour()
1052 {
1053 }
1054 
1055 
1056 QString KateTemplateHandler::MirrorBehaviour::getMirrorString(const QString &source)
1057 {
1058  QString ahead;
1059  QString output;
1060  QString finalOutput;
1061  int pos;
1062  int matchCounter = 0;
1063 
1064  switch (m_behaviour) {
1065 
1066  case Clone:
1067  return source;
1068  break;
1069 
1070  case Regexp: {
1071  ifDebug(kDebug() << "regexp " << m_search << " replacement " << m_replace;)
1072 
1073  if (m_global) {
1074  ahead = source;
1075 
1076  while (ahead.length() > 0) {
1077  if ((pos = m_expr.indexIn(ahead)) == -1) {
1078  return finalOutput + ahead;
1079  }
1080 
1081  QStringList results = m_expr.capturedTexts();
1082  output = KateRegExpSearch::buildReplacement(m_replace, results, ++matchCounter);
1083 
1084  finalOutput = finalOutput + ahead.left(pos) + output;
1085  ahead = ahead.mid(pos + m_expr.matchedLength());
1086  }
1087 
1088  return finalOutput;
1089  } else {
1090  if ((pos = m_expr.indexIn(source)) == -1) {
1091  return source;
1092  }
1093 
1094  QStringList results = m_expr.capturedTexts();
1095  output = KateRegExpSearch::buildReplacement(m_replace, results, 1);
1096  return source.left(pos) + output + source.mid(pos + m_expr.matchedLength());
1097  }
1098 
1099  break;
1100  }
1101 
1102  case Scripted: {
1103  KateTemplateScript *script = KateGlobal::self()->scriptManager()->templateScript(m_templateScript);
1104 
1105  if (script) {
1106  QString result = script->invoke(m_handler->view(), m_functionName, source);
1107 
1108  if (!result.isNull()) {
1109  return result;
1110  }
1111  }
1112 
1113  return source;
1114  }
1115 
1116  default:
1117  return QString();
1118  }
1119 
1120  return QString();
1121 }
1122 
1123 //END MIRROR BEHAVOUR
1124 
1125 
1126 #include "katetemplatehandler.moc"
1127 
1128 // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; replace-tabs on; replace-tabs on; replace-tabs on;
QObject::child
QObject * child(const char *objName, const char *inheritsClass, bool recursiveSearch) const
KateDocument::align
void align(KateView *view, const KTextEditor::Range &range)
Definition: katedocument.cpp:2909
QList::clear
void clear()
QString::indexOf
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
QEvent
customContains
static bool customContains(MovingRange *range, const Cursor &cursor)
just like Range::contains() but returns true when the cursor is at the end of the range ...
Definition: katetemplatehandler.cpp:48
QKeyEvent::modifiers
Qt::KeyboardModifiers modifiers() const
kateview.h
QEvent::type
Type type() const
getAttribute
Attribute::Ptr getAttribute(QColor color, int alpha=230)
Returns an attribute with color as background with alpha alpha value.
Definition: katetemplatehandler.cpp:421
KateDocument::newMovingCursor
virtual KTextEditor::MovingCursor * newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior=KTextEditor::MovingCursor::MoveOnInsert)
Create a new moving cursor for this document.
Definition: katedocument.cpp:4736
cmp_moving_ranges
static bool cmp_moving_ranges(const KTextEditor::MovingRange *r1, const KTextEditor::MovingRange *r2)
Definition: katetemplatehandler.cpp:42
KateRegExpSearch::buildReplacement
static QString buildReplacement(const QString &text, const QStringList &capturedTexts, int replacementCounter)
Returns a modified version of text where.
Definition: kateregexpsearch.cpp:537
KateDocument::newMovingRange
virtual KTextEditor::MovingRange * newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors=KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::EmptyBehavior emptyBehavior=KTextEditor::MovingRange::AllowEmpty)
Create a new moving range for this document.
Definition: katedocument.cpp:4741
QMap::contains
bool contains(const Key &key) const
QQueue
ifDebug
#define ifDebug(x)
Definition: katetemplatehandler.cpp:40
KateView::renderer
KateRenderer * renderer()
Definition: kateview.cpp:1664
KateView::isCompletionActive
virtual bool isCompletionActive() const
Definition: kateview.cpp:2341
katerenderer.h
QMap::values
QList< T > values() const
QChar
QFont
katetemplatescript.h
QMap< QString, QString >
QString::size
int size() const
katedocument.h
KateTemplateHandler
Inserts a template and offers advanced snippet features, like navigation and mirroring.
Definition: katetemplatehandler.h:73
QColor::setAlpha
void setAlpha(int alpha)
KateTemplateHandler::MirrorBehaviour::getMirrorString
QString getMirrorString(const QString &source)
Definition: katetemplatehandler.cpp:1056
QBrush
KateGlobal::self
static KateGlobal * self()
Kate Part Internal stuff ;)
Definition: kateglobal.cpp:465
KateUndoManager
KateUndoManager implements a document's history.
Definition: kateundomanager.h:45
KateTemplateHandler::KateTemplateHandler
KateTemplateHandler(KateView *view, const KTextEditor::Cursor &position, const QString &templateString, const QMap< QString, QString > &initialValues, KateUndoManager *undoManager, KateTemplateScript *templateScript)
Setup the template handler, insert the template string.
Definition: katetemplatehandler.cpp:60
QString::remove
QString & remove(int position, int n)
KateView::setCursorPosition
bool setCursorPosition(KTextEditor::Cursor position)
Definition: kateview.cpp:2418
QMap::clear
void clear()
QObject::disconnect
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
kateregexpsearch.h
QList::size
int size() const
QString::lastIndexOf
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
QString::isNull
bool isNull() const
QObject::event
virtual bool event(QEvent *e)
KateView::selectionRange
virtual const KTextEditor::Range & selectionRange() const
Definition: kateview.cpp:2815
QRegExp
KateDocument::replaceText
virtual bool replaceText(const KTextEditor::Range &range, const QString &s, bool block=false)
Definition: katedocument.cpp:4686
KateRendererConfig::templateBackgroundColor
const QColor & templateBackgroundColor() const
Definition: kateconfig.cpp:2550
QList::append
void append(const T &value)
QString::insert
QString & insert(int position, QChar ch)
QObject::installEventFilter
void installEventFilter(QObject *filterObj)
KateRenderer::config
KateRendererConfig * config() const
Configuration.
Definition: katerenderer.h:362
KateView::setSelection
virtual bool setSelection(const KTextEditor::Range &selection)
Definition: kateview.cpp:1956
KateHighlighting::attribute
int attribute(int context) const
Definition: katehighlight.cpp:1078
KateRendererConfig::templateEditablePlaceholderColor
const QColor & templateEditablePlaceholderColor() const
Definition: kateconfig.cpp:2558
QObject
kateglobal.h
QList::isEmpty
bool isEmpty() const
QString::isEmpty
bool isEmpty() const
QMultiMap::insert
QMap< Key, T >::iterator insert(const Key &key, const T &value)
QObject::eventFilter
virtual bool eventFilter(QObject *watched, QEvent *event)
QList::first
T & first()
KateTemplateHandler::MirrorBehaviour::~MirrorBehaviour
~MirrorBehaviour()
Definition: katetemplatehandler.cpp:1051
QString
QList
QColor
katetemplatehandler.h
KateView::selection
virtual bool selection() const
Definition: kateview.cpp:2033
KateTemplateHandler::eventFilter
virtual bool eventFilter(QObject *object, QEvent *event)
Provide keyboard interaction for the template handler.
Definition: katetemplatehandler.cpp:263
QStringList
KateView
Definition: kateview.h:77
QList::end
iterator end()
QKeyEvent::key
int key() const
QEvent::accept
void accept()
KateView::updateView
void updateView(bool changed=false)
Definition: kateview.cpp:1916
KateDocument
Definition: katedocument.h:74
QString::contains
bool contains(QChar ch, Qt::CaseSensitivity cs) const
KateTemplateHandler::view
KateView * view()
Definition: katetemplatehandler.cpp:1018
KateDocument::setUndoMergeAllEdits
void setUndoMergeAllEdits(bool merge)
Definition: katedocument.cpp:4722
QList::contains
bool contains(const T &value) const
KateUndoManager::undoSafePoint
void undoSafePoint()
Prevent latest KateUndoGroup from being merged with the next one.
Definition: kateundomanager.cpp:190
KateTemplateScript::invoke
QString invoke(KateView *view, const QString &functionName, const QString &srcText)
Definition: katetemplatescript.cpp:33
KateView::cursorPosition
KTextEditor::Cursor cursorPosition() const
Definition: kateview.cpp:2423
QString::replace
QString & replace(int position, int n, QChar after)
QKeyEvent
QList::takeLast
T takeLast()
KateRendererConfig
Definition: kateconfig.h:618
QString::mid
QString mid(int position, int n) const
KateUndoManager::isActive
bool isActive() const
Definition: kateundomanager.h:90
KateDocument::editEnd
void editEnd()
End a editor operation.
Definition: katedocument.cpp:796
QString::at
const QChar at(int position) const
QList::last
T & last()
KateView::selectionText
virtual QString selectionText() const
Definition: kateview.cpp:2041
QString::length
int length() const
KateDocument::views
virtual const QList< KTextEditor::View * > & views() const
Definition: katedocument.cpp:309
KateDocument::text
virtual QString text(const KTextEditor::Range &range, bool blockwise=false) const
Definition: katedocument.cpp:337
QString::left
QString left(int n) const
KateView::removeSelectionText
virtual bool removeSelectionText()
Definition: kateview.h:258
kateundomanager.h
KateView::doc
KateDocument * doc()
accessor to katedocument pointer
Definition: kateview.h:553
QMap::insert
iterator insert(const Key &key, const T &value)
KateRendererConfig::templateNotEditablePlaceholderColor
const QColor & templateNotEditablePlaceholderColor() const
Definition: kateconfig.cpp:2574
QMap::isEmpty
bool isEmpty() const
QMultiMap
QMultiMap::remove
int remove(const Key &key)
katescriptmanager.h
KateDocument::editStart
void editStart()
Enclose editor actions with editStart() and editEnd() to group them.
Definition: katedocument.cpp:776
KateTemplateHandler::MirrorBehaviour::MirrorBehaviour
MirrorBehaviour()
Definition: katetemplatehandler.cpp:1025
QObject::connect
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::parent
QObject * parent() const
KateTemplateScript
Definition: katetemplatescript.h:28
kateconfig.h
QList::removeOne
bool removeOne(const T &value)
QList::begin
iterator begin()
KateDocument::removeText
virtual bool removeText(const KTextEditor::Range &range, bool block=false)
Definition: katedocument.cpp:633
KateGlobal::scriptManager
KateScriptManager * scriptManager()
Global script collection.
Definition: kateglobal.h:321
KateRendererConfig::templateFocusedEditablePlaceholderColor
const QColor & templateFocusedEditablePlaceholderColor() const
Definition: kateconfig.cpp:2566
KateScriptManager::templateScript
KateTemplateScript * templateScript(KTextEditor::TemplateScript *templateScript)
Definition: katescriptmanager.cpp:410
QMap::value
const T value(const Key &key) const
KateTemplateHandler::~KateTemplateHandler
virtual ~KateTemplateHandler()
Cancels the template handler and cleans everything up.
Definition: katetemplatehandler.cpp:175
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sat May 9 2020 03:56:58 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

Kate

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

applications API Reference

Skip menu "applications API Reference"
  •   kate
  •       kate
  •   KTextEditor
  •   Kate
  • Konsole

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal