22 #include <QtCore/QQueue>
38 using namespace KTextEditor;
50 return range->
start() <= cursor && range->
end() >= cursor;
55 return range.
start() <= cursor && range.
end() >= cursor;
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)
86 if (initial_Values.contains(
"selection")) {
87 if (initial_Values[
"selection"].isEmpty()) {
100 this, SLOT(cleanupAndExit()));
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()) {
149 setupEventHandler(view);
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());
191 void KateTemplateHandler::cleanupAndExit()
201 if (!m_templateRanges.isEmpty()) {
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);
260 view->focusProxy()->installEventFilter(
this);
266 if (event->type() == QEvent::KeyPress) {
267 QKeyEvent *keyEvent =
static_cast<QKeyEvent*
>(event);
269 if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) {
278 if (event->type() == QEvent::ShortcutOverride) {
279 QKeyEvent *keyEvent =
static_cast<QKeyEvent*
>(event);
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();
315 return QObject::eventFilter(
object, event);
318 void KateTemplateHandler::jumpToPreviousRange()
322 if (cursor == *m_finalCursorPosition) {
324 setCurrentRange(m_masterRanges.last());
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());
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()) {
390 foreach(
MovingRange* childRange, m_templateRangesChildren[range]) {
393 if (m_masterRanges.contains(childRange)) {
402 range = m_templateRangesChildren[range].first();
408 if (m_uneditedRanges.contains(range))
415 m_lastCaretPosition = range->
start();
424 color.setAlpha(alpha);
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;
445 QQueue<QString> keyQueue;
446 QMultiMap<QString, Range> ranges;
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();
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);
625 }
else if (check_colon) {
626 key = key.left(pos_colon);
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;)
635 if (key.contains(
"@")) {
636 key = key.left(key.indexOf(
"@"));
642 if (!initialValues.contains(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()) {
666 int pos = searchReplace.indexOf(
"/");
668 for (
int epos = pos - 1; epos >= 0 && searchReplace.at(epos) ==
'\\'; epos--) {
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);
682 ifDebug(
kDebug() <<
"remaining characters in searchReplace" << searchReplace;)
690 int last_slash = searchReplace.lastIndexOf(
"/");
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]) {
726 if (!keyQueue.contains(key)) {
727 keyQueue.append(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 );
746 range_list.append(tmp);
749 while (!range_list.isEmpty()) {
750 ranges.insert(key, range_list.takeLast());
753 ranges.insert(key, tmp);
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);
785 if (finalCursorPosition.
isValid()) {
791 if (ranges.isEmpty()) {
800 editableAttribute->setDynamicAttribute(
810 foreach(
const QString& key, keyQueue) {
816 if (values.size() > 1) {
818 for (
int i = 0; i < values.size(); ++i) {
822 m_templateRangesChildren[parent].push_back(range);
823 m_templateRangesChildToParent[range] = parent;
826 if (i == values.size() - 1) {
828 m_uneditedRanges.append(range);
829 m_masterRanges.append(range);
832 m_mirrorBehaviour.insert(range, mirrorBehaviourBuildHelper[values[i]]);
838 m_uneditedRanges.append(parent);
839 m_masterRanges.append(parent);
842 m_templateRanges.append(parent);
851 foreach(
const Range& spacer, spacers) {
854 m_spacersMovingRanges.append(r);
859 setCurrentRange(m_templateRanges.first());
860 mirrorBehaviourBuildHelper.clear();
865 m_initialRemodify =
true;
867 slotTextChanged(doc(),
Range(*smr));
870 m_initialRemodify =
false;
874 void KateTemplateHandler::slotTextChanged(
Document* document,
const Range& range)
876 Q_ASSERT(document == doc());
885 if (!m_initialRemodify) {
886 if ((!m_editWithUndo && doc()->isEditRunning()) || range.
isEmpty()) {
887 ifDebug(
kDebug() <<
"slotTextChanged returning prematurely";)
898 if (range.
end() == *m_finalCursorPosition) {
899 ifDebug(
kDebug() <<
"editing at final cursor position, exiting.";)
927 if (m_templateRangesChildren[parent].isEmpty()) {
929 if (!m_initialRemodify)
930 m_uneditedRanges.removeOne(parent);
935 ifDebug(
kDebug() <<
"looking for adjacent mirror to" << *baseRange;)
936 foreach(
MovingRange* child, m_templateRangesChildren[parent]) {
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) {
974 m_uneditedRanges.removeOne(baseRange);
976 if (leftAdjacentRange) {
978 ifDebug(
kDebug() <<
"removing edited range" << range <<
"from left adjacent range" << *leftAdjacentRange;)
979 leftAdjacentRange->setRange(
Range(leftAdjacentRange->start(), range.
start()));
983 syncMirroredRanges(baseRange);
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)
1037 m_global = flags.contains(
"g");
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);
1108 if (!result.isNull()) {
1126 #include "katetemplatehandler.moc"
void align(KateView *view, const KTextEditor::Range &range)
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 ...
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.
virtual int column() const =0
KateRenderer * renderer()
virtual bool isCompletionActive() const
Inserts a template and offers advanced snippet features, like navigation and mirroring.
QString getMirrorString(const QString &source)
const Range toRange() const
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.
bool setCursorPosition(KTextEditor::Cursor position)
static QDebug kDebug(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
KSharedConfigPtr config()
virtual const KTextEditor::Range & selectionRange() const
virtual bool replaceText(const KTextEditor::Range &range, const QString &s, bool block=false)
const QColor & templateBackgroundColor() const
virtual bool isValid() const
KateRendererConfig * config() const
Configuration.
virtual bool setSelection(const KTextEditor::Range &selection)
int attribute(int context) const
const QColor & templateEditablePlaceholderColor() const
void output(QList< Action > actions, QHash< QString, QString > domain)
virtual void setAttribute(Attribute::Ptr attribute)=0
virtual bool selection() const
virtual bool eventFilter(QObject *object, QEvent *event)
Provide keyboard interaction for the template handler.
void updateView(bool changed=false)
void setUndoMergeAllEdits(bool merge)
void undoSafePoint()
Prevent latest KateUndoGroup from being merged with the next one.
QString invoke(KateView *view, const QString &functionName, const QString &srcText)
KAction * replace(const QObject *recvr, const char *slot, QObject *parent)
KTextEditor::Cursor cursorPosition() const
virtual const MovingCursor & start() const =0
virtual int line() const =0
void editEnd()
End a editor operation.
virtual QString selectionText() const
virtual const QList< KTextEditor::View * > & views() const
virtual QString text(const KTextEditor::Range &range, bool blockwise=false) const
static QDebug kWarning(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
virtual bool removeSelectionText()
KateDocument * doc()
accessor to katedocument pointer
const QColor & templateNotEditablePlaceholderColor() const
bool contains(const Range &range) const
virtual const MovingCursor & end() const =0
void editStart()
Enclose editor actions with editStart() and editEnd() to group them.
virtual bool removeText(const KTextEditor::Range &range, bool block=false)
KateScriptManager * scriptManager()
Global script collection.
const QColor & templateFocusedEditablePlaceholderColor() const
KateTemplateScript * templateScript(KTextEditor::TemplateScript *templateScript)
virtual ~KateTemplateHandler()
Cancels the template handler and cleans everything up.