22 #include <QtCore/QQueue>
24 #include <ktexteditor/movingcursor.h>
25 #include <ktexteditor/movingrange.h>
38 using namespace KTextEditor;
42 static bool cmp_moving_ranges(
const KTextEditor::MovingRange* r1,
const KTextEditor::MovingRange* r2)
44 return r1->start() < r2->start();
50 return range->start() <= cursor && range->end() >= cursor;
53 static bool customContains(
const KTextEditor::Range &range,
const Cursor& cursor)
55 return range.start() <= cursor && range.end() >= cursor;
61 const Cursor& position,
68 , m_undoManager(undoManager)
69 , m_wholeTemplateRange(0)
70 , m_finalCursorPosition(0)
71 , m_lastCaretPosition(position)
72 , m_isMirroring(false)
73 , m_editWithUndo(false)
75 , m_templateScript(templateScript)
82 ifDebug(kDebug() << templateString << initialValues;)
86 if (initial_Values.contains(
"selection")) {
87 if (initial_Values[
"selection"].isEmpty()) {
97 ifDebug(kDebug() << initial_Values;)
99 connect(doc(), SIGNAL(aboutToReload(KTextEditor::Document*)),
100 this, SLOT(cleanupAndExit()));
102 connect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
103 this, SLOT(slotTemplateInserted(KTextEditor::Document*,KTextEditor::Range)));
114 if (!doc()->insertText(m_lastCaretPosition, templateString)) {
123 Q_ASSERT(m_wholeTemplateRange);
131 doc()->
align(m_view, *m_wholeTemplateRange);
140 handleTemplateString(initial_Values);
145 if (!initialValues.
isEmpty()) {
147 if (!m_templateRanges.
isEmpty()) {
148 foreach(View* view, doc()->
views()) {
149 setupEventHandler(view);
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)));
159 setEditWithUndo(undoManager->
isActive());
161 connect(undoManager, SIGNAL(isActiveChanged(
bool)),
162 this, SLOT(setEditWithUndo(
bool)));
166 jumpToFinalCursorPosition();
179 void KateTemplateHandler::slotTemplateInserted(Document *document,
const Range& range)
181 Q_ASSERT(document == doc());
183 ifDebug(kDebug() <<
"template range inserted" << range;)
185 m_wholeTemplateRange = doc()->
newMovingRange(range, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
187 disconnect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)),
188 this, SLOT(slotTemplateInserted(KTextEditor::Document*,KTextEditor::Range)));
191 void KateTemplateHandler::cleanupAndExit()
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)));
201 if (!m_templateRanges.
isEmpty()) {
202 foreach(MovingRange* range, m_templateRanges) {
204 foreach(MovingRange*
child, m_templateRangesChildren[range])
210 m_templateRanges.
clear();
211 m_templateRangesChildren.
clear();
212 m_templateRangesChildToParent.
clear();
216 Q_ASSERT(m_templateRangesChildren.
isEmpty());
217 Q_ASSERT(m_templateRangesChildToParent.
isEmpty());
219 if (!m_spacersMovingRanges.
isEmpty()) {
220 foreach(MovingRange* range, m_spacersMovingRanges) {
225 m_spacersMovingRanges.
clear();
228 delete m_wholeTemplateRange;
229 delete m_finalCursorPosition;
233 void KateTemplateHandler::jumpToFinalCursorPosition()
243 return m_view->
doc();
246 void KateTemplateHandler::setEditWithUndo(
const bool &enabled)
248 m_editWithUndo = enabled;
251 void KateTemplateHandler::slotViewCreated(Document* document, View* view)
253 Q_ASSERT(document == doc());
255 setupEventHandler(view);
266 if (event->
type() == QEvent::KeyPress) {
269 if (keyEvent->
key() == Qt::Key_Tab || keyEvent->
key() == Qt::Key_Backtab) {
278 if (event->
type() == QEvent::ShortcutOverride) {
281 if (keyEvent->
key() == Qt::Key_Return && keyEvent->
modifiers() & Qt::AltModifier) {
283 jumpToFinalCursorPosition();
288 }
else if (keyEvent->
key() == Qt::Key_Escape) {
291 jumpToFinalCursorPosition();
298 if (keyEvent->
modifiers() & Qt::Key_Shift) {
299 jumpToPreviousRange();
309 jumpToPreviousRange();
318 void KateTemplateHandler::jumpToPreviousRange()
322 if (cursor == *m_finalCursorPosition) {
324 setCurrentRange(m_masterRanges.
last());
328 MovingRange* previousRange = 0;
330 foreach(MovingRange* range, m_masterRanges) {
331 if (range->start() >= cursor) {
335 if (!previousRange || range->start() > previousRange->start()) {
336 previousRange = range;
338 if (m_templateRangesChildToParent.
value(previousRange)) previousRange = m_templateRangesChildToParent.
value(previousRange);
343 setCurrentRange(previousRange);
347 jumpToFinalCursorPosition();
351 void KateTemplateHandler::jumpToNextRange()
355 if (cursor == *m_finalCursorPosition) {
357 setCurrentRange(m_masterRanges.
first());
361 MovingRange* nextRange = 0;
363 foreach(MovingRange* range, m_masterRanges) {
364 if (range->start() <= cursor) {
368 if (!nextRange || range->start() < nextRange->start()) {
374 if (m_templateRangesChildToParent.
value(nextRange)) nextRange = m_templateRangesChildToParent.
value(nextRange);
376 setCurrentRange(nextRange);
380 jumpToFinalCursorPosition();
384 void KateTemplateHandler::setCurrentRange(MovingRange* range)
386 if (!m_templateRangesChildren[range].isEmpty()) {
387 ifDebug(kDebug() <<
"looking for mirroring range";)
390 foreach(MovingRange* childRange, m_templateRangesChildren[range]) {
391 ifDebug(kDebug() <<
"checking range equality";)
393 if (m_masterRanges.
contains(childRange)) {
394 ifDebug(kDebug() <<
"found master range";)
402 range = m_templateRangesChildren[range].first();
408 if (m_uneditedRanges.
contains(range))
415 m_lastCaretPosition = range->start();
423 Attribute::Ptr
attribute(
new Attribute());
425 attribute->setBackground(
QBrush(color));
431 QString templateString = doc()->
text(*m_wholeTemplateRange);
433 int line = m_wholeTemplateRange->start().line();
434 int column = m_wholeTemplateRange->start().column();
439 bool lastWasBrace =
false;
452 Cursor finalCursorPosition = Cursor::invalid();
460 for (
int i = 0; i < templateString.
size(); ++i) {
461 ifDebug(kDebug() <<
"checking character:" << templateString[i];)
463 if (templateString[i] ==
'\n') {
464 lastWasBrace =
false;
468 if (startPos != -1) {
473 }
else if ((templateString[i] ==
'%' || templateString[i] ==
'$')
474 && i + 1 < templateString.
size() && templateString[i+1] ==
'{') {
479 while (i - escapeChars > 0 && templateString[i - escapeChars - 1] ==
'\\') {
483 if (escapeChars > 0) {
484 ifDebug(kDebug() <<
"found" << escapeChars <<
"escape chars at " << templateString.
mid(i - escapeChars - 10, escapeChars + 10);)
487 int toRemove = (escapeChars + 1) / 2;
488 ifDebug(kDebug() <<
"will remove" << toRemove <<
"of those escape chars";)
489 templateString.
remove(i - escapeChars, toRemove);
494 if (escapeChars % 2 == 0) {
497 templateString.
insert(i,
" ");
498 spacers.
append(Range(line, column, line, column + 1));
506 lastWasBrace =
false;
512 }
else if ((startPos != -1) && (templateString[i] ==
':')) {
515 int backslash_count = 0;
517 for (;i < templateString.
size();i++, column++) {
518 if (templateString[i] ==
'\n') {
522 if (startPos != -1) {
530 if (templateString[i] ==
'}') {
531 if ((backslash_count % 2) == 0) {
540 }
else if (templateString[i] ==
'\\') {
548 }
else if ((startPos != -1) && (templateString[i] ==
'/')) {
551 int backslash_count = 0;
554 for (;i < templateString.
size();i++, column++) {
555 if (templateString[i] ==
'\n') {
559 if (startPos != -1) {
567 if (templateString[i] ==
'/') {
568 if ((backslash_count % 2) == 0)
573 }
else if (templateString[i] ==
'\\') {
580 if (slashcount == 3) {
586 }
else if (templateString[i] ==
'}' && startPos != -1) {
588 bool force_first =
false;
590 QString key = templateString.
mid(startPos + 2, i - (startPos + 2));
591 int keyLength = key.
length();
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(
"`");
601 if ((pos_slash == -1) && (pos_colon == -1)) {
603 }
else if ((pos_slash != -1) && (pos_colon == -1)) {
605 }
else if ((pos_slash == -1) && (pos_colon != -1)) {
608 if (pos_colon < pos_slash) {
615 if (!check_slash && !check_colon && pos_backtick >= 0) {
616 check_backtick =
true;
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;)
640 ifDebug(kDebug() <<
"real key found:" << key;)
643 kWarning() <<
"unknown variable key:" << key;
644 }
else if (key ==
"cursor") {
645 finalCursorPosition = Cursor(line, column - keyLength - 2);
647 templateString.
remove(startPos, i - startPos + 1);
650 column -= 2 + keyLength;
653 MirrorBehaviour behaviour;
655 if (!searchReplace.
isEmpty()) {
657 bool searchValid =
false;
659 bool replaceValid =
false;
663 while (!searchReplace.
isEmpty()) {
664 ifDebug(kDebug() <<
"searchReplace=" << searchReplace;)
666 int pos = searchReplace.
indexOf(
"/");
668 for (
int epos = pos - 1; epos >= 0 && searchReplace.
at(epos) ==
'\\'; epos--) {
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;)
678 search += searchReplace.
left(pos);
679 searchReplace = searchReplace.
mid(pos + 1);
681 ifDebug(kDebug() <<
"final search string is=" << search;)
682 ifDebug(kDebug() <<
"remaining characters in searchReplace" << searchReplace;)
692 if (last_slash != -1) {
693 replace = searchReplace.
left(last_slash);
695 flags = searchReplace.
mid(last_slash + 1);
699 if (searchValid && replaceValid) {
700 behaviour = MirrorBehaviour(search, replace, flags);
703 }
else if (!functionName.
isEmpty()) {
704 behaviour = MirrorBehaviour(m_templateScript, functionName,
this);
707 const QString initialVal = behaviour.getMirrorString(initialValues[key]);
710 QChar c = templateString[startPos];
713 templateString.
replace(startPos, i - startPos + 1, initialVal);
716 i -= 3 + keyLength - initialVal.
length();
720 column -= 2 + keyLength - initialVal.
length();
724 ifDebug(kDebug() <<
"char is:" << c <<
"initial value is:" << initialValues[key] <<
" after fixup is:" << initialVal;)
725 if (c ==
'$' || key == initialValues[key]) {
731 int endColumn = column - initialVal.
length();
733 for (
int j = 0; j < initialVal.
length(); ++j) {
734 if (initialVal.
at(j) ==
'\n') {
741 Range tmp = Range(line, column - initialVal.
length(), endLine, endColumn );
742 ifDebug(kDebug() <<
"range is:" << tmp;)
749 while (!range_list.
isEmpty()) {
756 mirrorBehaviourBuildHelper.
insert(tmp, behaviour);
762 ifDebug(kDebug() <<
"i=" << i <<
" template size=" << templateString.
size();)
765 lastWasBrace =
false;
769 if (lastWasBrace && (!finalCursorPosition.isValid())) {
770 templateString +=
" ";
771 spacers.
append(Range(line, column, line, column + 1));
775 doc()->
replaceText(*m_wholeTemplateRange, templateString);
777 Q_ASSERT(!m_wholeTemplateRange->toRange().isEmpty());
785 if (finalCursorPosition.isValid()) {
800 editableAttribute->setDynamicAttribute(
810 foreach(
const QString& key, keyQueue) {
816 if (values.
size() > 1) {
818 for (
int i = 0; i < values.
size(); ++i) {
819 MovingRange* range = doc()->
newMovingRange(values[i], MovingRange::ExpandLeft | MovingRange::ExpandRight);
822 m_templateRangesChildren[
parent].push_back(range);
823 m_templateRangesChildToParent[range] =
parent;
826 if (i == values.
size() - 1) {
827 range->setAttribute(editableAttribute);
828 m_uneditedRanges.
append(range);
829 m_masterRanges.
append(range);
831 range->setAttribute(mirroredAttribute);
832 m_mirrorBehaviour.insert(range, mirrorBehaviourBuildHelper[values[i]]);
837 parent->setAttribute(editableAttribute);
838 m_uneditedRanges.
append(parent);
839 m_masterRanges.
append(parent);
842 m_templateRanges.
append(parent);
846 Attribute::Ptr
attribute(
new Attribute());
851 foreach(
const Range& spacer, spacers) {
854 m_spacersMovingRanges.
append(r);
859 setCurrentRange(m_templateRanges.
first());
860 mirrorBehaviourBuildHelper.
clear();
865 m_initialRemodify =
true;
866 foreach(MovingRange* smr, m_masterRanges) {
867 slotTextChanged(doc(), Range(*smr));
870 m_initialRemodify =
false;
874 void KateTemplateHandler::slotTextChanged(Document* document,
const Range& range)
876 Q_ASSERT(document == doc());
878 ifDebug(kDebug() <<
"text changed" << document << range;)
881 ifDebug(kDebug() <<
"mirroring - ignoring edit";)
885 if (!m_initialRemodify) {
886 if ((!m_editWithUndo && doc()->isEditRunning()) || range.isEmpty()) {
887 ifDebug(kDebug() <<
"slotTextChanged returning prematurely";)
892 if (m_wholeTemplateRange->toRange().isEmpty()) {
893 ifDebug(kDebug() <<
"template range got deleted, exiting";)
898 if (range.end() == *m_finalCursorPosition) {
899 ifDebug(kDebug() <<
"editing at final cursor position, exiting.";)
904 if (!m_wholeTemplateRange->toRange().contains(range.start())) {
906 ifDebug(kDebug() << range <<
"not contained in" << m_wholeTemplateRange->toRange();)
911 ifDebug(kDebug() <<
"see if we have to mirror the edit";)
921 MovingRange* baseRange = 0;
923 MovingRange* leftAdjacentRange = 0;
925 foreach(MovingRange* parent, m_templateRanges) {
927 if (m_templateRangesChildren[parent].isEmpty()) {
929 if (!m_initialRemodify)
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;
949 foreach(MovingRange* child, m_templateRangesChildren[parent]) {
956 if (baseRange && baseRange->end() != range.end()) {
959 }
else if (baseRange && (baseRange->isEmpty() || range == *baseRange)) {
967 }
else if (baseRange) {
976 if (leftAdjacentRange) {
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;)
983 syncMirroredRanges(baseRange);
985 if (range.start() == baseRange->start()) {
991 ifDebug(kDebug() <<
"no range found to mirror this edit";)
995 void KateTemplateHandler::syncMirroredRanges(MovingRange* range)
997 Q_ASSERT(m_templateRanges.
contains(m_templateRangesChildToParent[range]));
999 m_isMirroring =
true;
1003 ifDebug(kDebug() <<
"mirroring" << newText <<
"from" << *range;)
1005 foreach(MovingRange* sibling, m_templateRangesChildren[m_templateRangesChildToParent[range]]) {
1006 if (sibling != range) {
1007 doc()->
replaceText(*sibling, m_mirrorBehaviour[sibling].getMirrorString(newText));
1014 m_isMirroring =
false;
1026 : m_behaviour(Clone)
1033 : m_behaviour(Regexp)
1035 , m_replace(replacement)
1038 m_expr =
QRegExp(regexp, flags.
contains(
"i") ? Qt::CaseInsensitive : Qt::CaseSensitive, QRegExp::RegExp2);
1044 : m_behaviour(Scripted)
1045 , m_templateScript(templateScript)
1046 , m_functionName(functionName)
1047 , m_handler(handler)
1062 int matchCounter = 0;
1064 switch (m_behaviour) {
1071 ifDebug(kDebug() <<
"regexp " << m_search <<
" replacement " << m_replace;)
1076 while (ahead.
length() > 0) {
1077 if ((pos = m_expr.indexIn(ahead)) == -1) {
1078 return finalOutput + ahead;
1084 finalOutput = finalOutput + ahead.
left(pos) + output;
1085 ahead = ahead.
mid(pos + m_expr.matchedLength());
1090 if ((pos = m_expr.indexIn(source)) == -1) {
1096 return source.
left(pos) + output + source.
mid(pos + m_expr.matchedLength());
1106 QString result = script->
invoke(m_handler->view(), m_functionName, source);
1126 #include "katetemplatehandler.moc"
QObject * child(const char *objName, const char *inheritsClass, bool recursiveSearch) const
void align(KateView *view, const KTextEditor::Range &range)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
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 ...
Qt::KeyboardModifiers modifiers() const
Attribute::Ptr getAttribute(QColor color, int alpha=230)
Returns an attribute with color as background with alpha alpha value.
virtual KTextEditor::MovingCursor * newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior=KTextEditor::MovingCursor::MoveOnInsert)
Create a new moving cursor for this document.
static bool cmp_moving_ranges(const KTextEditor::MovingRange *r1, const KTextEditor::MovingRange *r2)
static QString buildReplacement(const QString &text, const QStringList &capturedTexts, int replacementCounter)
Returns a modified version of text where.
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.
bool contains(const Key &key) const
KateRenderer * renderer()
virtual bool isCompletionActive() const
QList< T > values() const
Inserts a template and offers advanced snippet features, like navigation and mirroring.
QString getMirrorString(const QString &source)
static KateGlobal * self()
Kate Part Internal stuff ;)
KateUndoManager implements a document's history.
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.
QString & remove(int position, int n)
bool setCursorPosition(KTextEditor::Cursor position)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
virtual bool event(QEvent *e)
virtual const KTextEditor::Range & selectionRange() const
virtual bool replaceText(const KTextEditor::Range &range, const QString &s, bool block=false)
const QColor & templateBackgroundColor() const
void append(const T &value)
QString & insert(int position, QChar ch)
void installEventFilter(QObject *filterObj)
KateRendererConfig * config() const
Configuration.
virtual bool setSelection(const KTextEditor::Range &selection)
int attribute(int context) const
const QColor & templateEditablePlaceholderColor() const
QMap< Key, T >::iterator insert(const Key &key, const T &value)
virtual bool eventFilter(QObject *watched, QEvent *event)
virtual bool selection() const
virtual bool eventFilter(QObject *object, QEvent *event)
Provide keyboard interaction for the template handler.
void updateView(bool changed=false)
bool contains(QChar ch, Qt::CaseSensitivity cs) const
void setUndoMergeAllEdits(bool merge)
bool contains(const T &value) const
void undoSafePoint()
Prevent latest KateUndoGroup from being merged with the next one.
QString invoke(KateView *view, const QString &functionName, const QString &srcText)
KTextEditor::Cursor cursorPosition() const
QString & replace(int position, int n, QChar after)
QString mid(int position, int n) const
void editEnd()
End a editor operation.
const QChar at(int position) const
virtual QString selectionText() const
virtual const QList< KTextEditor::View * > & views() const
virtual QString text(const KTextEditor::Range &range, bool blockwise=false) const
QString left(int n) const
virtual bool removeSelectionText()
KateDocument * doc()
accessor to katedocument pointer
iterator insert(const Key &key, const T &value)
const QColor & templateNotEditablePlaceholderColor() const
int remove(const Key &key)
void editStart()
Enclose editor actions with editStart() and editEnd() to group them.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool removeOne(const T &value)
virtual bool removeText(const KTextEditor::Range &range, bool block=false)
KateScriptManager * scriptManager()
Global script collection.
const QColor & templateFocusedEditablePlaceholderColor() const
KateTemplateScript * templateScript(KTextEditor::TemplateScript *templateScript)
const T value(const Key &key) const
virtual ~KateTemplateHandler()
Cancels the template handler and cleans everything up.