1 /***********************************************************************************************
2   Copyright (C) 2004-2012 by Holger Danielsson (holger.danielsson@versanet.de)
3                 2008-2013 by Michel Ludwig (michel.ludwig@kdemail.net)
4  ***********************************************************************************************/
5 
6 /***************************************************************************
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  ***************************************************************************/
14 
15 #include "editorextension.h"
16 
17 #include <QFileInfo>
18 #include <QClipboard>
19 
20 #include <QApplication>
21 #include <KTextEditor/CodeCompletionInterface>
22 #include <KTextEditor/Document>
23 #include <KTextEditor/View>
24 #include <KTextEditor/Range>
25 #include <KTextEditor/Cursor>
26 #include <KLocalizedString>
27 
28 
29 #include "errorhandler.h"
30 #include "codecompletion.h"
31 #include "kile.h"
32 #include "kileactions.h"
33 #include "kileconfig.h"
34 #include "kileextensions.h"
35 #include "kileinfo.h"
36 #include "kiletool_enums.h"
37 #include "kileviewmanager.h"
38 #include "quickpreview.h"
39 #include "widgets/konsolewidget.h"
40 
41 /*
42  * FIXME: The code in this file should be reworked completely. Once we've got a better parser
43  *        most of the code in here should also be superfluous.
44  */
45 
46 namespace KileDocument
47 {
48 
EditorExtension(KileInfo * info)49 EditorExtension::EditorExtension(KileInfo *info) : m_ki(info)
50 {
51     m_latexCommands = m_ki->latexCommands();
52 
53     // init regexp
54     m_reg.setPattern("(\\\\(begin|end)\\s*\\{([A-Za-z]+\\*?)\\})|(\\\\\\[|\\\\\\])");
55     //                1    2                 3                   4
56     m_regexpEnter.setPattern("^(.*)((\\\\begin\\s*\\{([^\\{\\}]*)\\})|(\\\\\\[))");
57     //                         1   23                 4               5
58 
59     // init double quotes
60     m_quoteListI18N // this is shown in the configuration dialog
61             << i18n("English quotes:   ``   &apos;&apos;")
62             << i18n("French quotes:   &quot;&lt;   &quot;&gt;")
63             << i18n("German quotes:   &quot;`   &quot;&apos;")
64             << i18n("French quotes (long):   \\flqq   \\frqq")
65             << i18n("German quotes (long):   \\glqq   \\grqq")
66             << i18n("Icelandic quotes (v1):   \\ilqq   \\irqq")
67             << i18n("Icelandic quotes (v2):   \\iflqq   \\ifrqq")
68             << i18n("Czech quotes:   \\uv{}")
69             << i18n("csquotes package:   \\enquote{}");
70 
71 
72     m_quoteList
73             << QPair<QString, QString>("``", "''")
74             << QPair<QString, QString>("\"<", "\">")
75             << QPair<QString, QString>("\"`", "\"'")
76             << QPair<QString, QString>("\\flqq", "\\frqq")
77             << QPair<QString, QString>("\\glqq", "\\grqq")
78             << QPair<QString, QString>("\\ilqq", "\\irqq")
79             << QPair<QString, QString>("\\iflqq", "\\ifrqq")
80             << QPair<QString, QString>("\\uv{", "}")
81             << QPair<QString, QString>("\\enquote{", "}");
82 
83     readConfig();
84 }
85 
~EditorExtension()86 EditorExtension::~EditorExtension()
87 {
88 }
89 
90 //////////////////// read configuration ////////////////////
91 
readConfig()92 void EditorExtension::readConfig()
93 {
94     // init insertion of double quotes
95     initDoubleQuotes();
96 
97     // allow special chars?
98     m_specialCharacters = KileConfig::insertSpecialCharacters();
99 
100     // calculate indent for autoindent of environments
101     m_envAutoIndent.clear();
102     if(KileConfig::envIndentation()) {
103         if(KileConfig::envIndentSpaces()) {
104             int num = KileConfig::envIndentNumSpaces();
105             if(num < 1 || num > 9) {
106                 num = 1;
107             }
108             m_envAutoIndent.fill(' ', num);
109         }
110         else {
111             m_envAutoIndent = "\t";
112         }
113     }
114 }
115 
insertTag(const KileAction::TagData & data,KTextEditor::View * view)116 void EditorExtension::insertTag(const KileAction::TagData& data, KTextEditor::View *view)
117 {
118     KTextEditor::Document *doc = view->document();
119     if(!doc) {
120         return;
121     }
122 
123     //whether or not to wrap tag around selection
124     bool wrap = !data.tagEnd.isNull() && view->selection();
125 
126     //%C before or after the selection
127     bool before = data.tagBegin.count("%C");
128     bool after = data.tagEnd.count("%C");
129 
130     //save current cursor position
131     KTextEditor::Cursor cursor = view->cursorPosition();
132     int para = cursor.line();
133     int para_begin = para;
134     int index = cursor.column();
135     int index_begin = index;
136     int para_end = 0;
137     int index_cursor = index;
138     int para_cursor = index;
139     // offset for autoindentation of environments
140     int dxIndentEnv = 0;
141 
142     // environment tag
143     bool envtag = data.tagBegin.count("%E") || data.tagEnd.count("%E");
144     QString whitespace = getWhiteSpace( doc->line(para).left(index) );
145 
146     //if there is a selection act as if cursor is at the beginning of selection
147     if (wrap) {
148         KTextEditor::Range selectionRange = view->selectionRange();
149         index = selectionRange.start().column();
150         para  = selectionRange.start().line();
151         para_end = selectionRange.end().line();
152     }
153 
154     QString ins = data.tagBegin;
155     QString tagEnd = data.tagEnd;
156 
157     //start an atomic editing sequence
158     KTextEditor::Document::EditingTransaction transaction(doc);
159 
160     //cut the selected text
161     QString trailing;
162     if(wrap) {
163         QString sel = view->selectionText();
164         view->removeSelectionText();
165 
166         // no autoindentation of environments, when text is selected
167         if(envtag) {
168             ins.remove("%E");
169             tagEnd.remove("%E\n");
170         }
171 
172         // strip one of two consecutive line ends
173         int len = sel.length();
174         if(tagEnd.at(0)=='\n' && len > 0 && sel.indexOf('\n',-1) == len - 1) {
175             sel.truncate( len-1 );
176         }
177 
178         // now add the selection
179         ins += sel;
180 
181         // place the cursor behind this tag, if there is no other wish
182         if(!before && !after) {
183             trailing = "%C";
184             after = true;
185         }
186     }
187     else if(envtag) {
188         ins.replace("%E",whitespace+m_envAutoIndent);
189         tagEnd.replace("%E",whitespace+m_envAutoIndent);
190         if(data.dy > 0) {
191             dxIndentEnv = whitespace.length() + m_envAutoIndent.length();
192         }
193     }
194 
195     tagEnd.replace("\\end{",whitespace+"\\end{");
196     ins += tagEnd + trailing;
197 
198     //do some replacements
199     QFileInfo fi( doc->url().toLocalFile());
200     ins.replace("%S", fi.completeBaseName());
201     ins.replace("%B", s_bullet);
202 
203     //insert first part of tag at cursor position
204     doc->insertText(KTextEditor::Cursor(para, index), ins);
205 
206     //move cursor to the new position
207     if(before || after) {
208         int n = data.tagBegin.count("\n")+ data.tagEnd.count("\n");
209         if(wrap) {
210             n += para_end > para ? para_end-para : para-para_end;
211         }
212         for (int line = para_begin; line <= para_begin+n; ++line) {
213             if(doc->line(line).count("%C")) {
214                 int i=doc->line(line).indexOf("%C");
215                 para_cursor = line;
216                 index_cursor = i;
217                 doc->removeText(KTextEditor::Range(line, i, line, i+2));
218                 break;
219             }
220             index_cursor=index;
221             para_cursor=line;
222         }
223     }
224     else {
225         int py = para_begin, px = index_begin;
226         if(wrap) { //act as if cursor was at beginning of selected text (which is the point where the tagBegin is inserted)
227             py = para;
228             px = index;
229         }
230         para_cursor = py+data.dy;
231         index_cursor = px+data.dx+dxIndentEnv;
232     }
233 
234     //end the atomic editing sequence
235     transaction.finish();
236 
237     //set the cursor position (it is important that this is done outside of the atomic editing sequence)
238     view->setCursorPosition(KTextEditor::Cursor(para_cursor, index_cursor));
239 
240     view->removeSelection();
241 }
242 
243 //////////////////// goto environment tag (begin or end) ////////////////////
244 
245 // goto the next non-nested environment tag
246 
determineView(KTextEditor::View * view)247 KTextEditor::View* EditorExtension::determineView(KTextEditor::View *view)
248 {
249     if (!view) {
250         view = m_ki->viewManager()->currentTextView();
251     }
252 
253     m_overwritemode = (!view) ? false : (view->viewMode() == KTextEditor::View::NormalModeOverwrite);
254 
255     return view;
256 }
257 
gotoEnvironment(bool backwards,KTextEditor::View * view)258 void EditorExtension::gotoEnvironment(bool backwards, KTextEditor::View *view)
259 {
260     view = determineView(view);
261     if(!view) {
262         return;
263     }
264 
265     uint row,col;
266     EnvData env;
267     bool found;
268 
269     // get current position
270     KTextEditor::Document *doc = view->document();
271     KTextEditor::Cursor cursor = view->cursorPosition();
272     row = cursor.line();
273     col = cursor.column();
274 
275 
276     // start searching
277     if(backwards) {
278         found = findBeginEnvironment(doc,row,col,env);
279         //KILE_DEBUG_MAIN << "   goto begin env:  " << env.row << "/" << env.col;
280 
281     }
282     else {
283         found = findEndEnvironment(doc,row,col,env);
284         env.col += env.len;
285     }
286 
287     if(found) {
288         view->setCursorPosition(KTextEditor::Cursor(env.row, env.col));
289     }
290 }
291 
292 // match the opposite environment tag
293 
matchEnvironment(KTextEditor::View * view)294 void EditorExtension::matchEnvironment(KTextEditor::View *view)
295 {
296     view = determineView(view);
297     if(!view) {
298         return;
299     }
300 
301     uint row,col;
302     EnvData env;
303 
304     // get current position
305     KTextEditor::Document *doc = view->document();
306     KTextEditor::Cursor cursor = view->cursorPosition();
307     row = cursor.line();
308     col = cursor.column();
309 
310     // we only start, when we are at an environment tag
311     if(!isEnvironmentPosition(doc, row, col, env)) {
312         return;
313     }
314 
315     gotoEnvironment(env.tag != EnvBegin, view);
316 }
317 
318 //////////////////// close opened environments  ////////////////////
319 
320 // search for the last opened environment and close it
321 
closeEnvironment(KTextEditor::View * view)322 void EditorExtension::closeEnvironment(KTextEditor::View *view)
323 {
324     view = determineView(view);
325     if(!view) {
326         return;
327     }
328 
329     int row, col, currentRow, currentCol;
330     QString name;
331 
332     KTextEditor::Cursor cursor = view->cursorPosition();
333     currentRow = cursor.line();
334     currentCol = cursor.column();
335 
336     if(findOpenedEnvironment(row, col, name, view)) {
337         if(name == "\\[") {
338             view->document()->insertText(KTextEditor::Cursor(currentRow, currentCol), "\\]");
339         }
340         else {
341             view->document()->insertText(KTextEditor::Cursor(currentRow, currentCol), "\\end{" + name + '}');
342         }
343 // 		view->setCursorPosition(KTextEditor::Cursor(row + 1, 0));
344     }
345 }
346 
347 // close all opened environments
348 
closeAllEnvironments(KTextEditor::View * view)349 void EditorExtension::closeAllEnvironments(KTextEditor::View *view)
350 {
351     view = determineView(view);
352     if(!view) {
353         return;
354     }
355 
356     QStringList envlist = findOpenedEnvironmentList(view, true);
357     if(envlist.count() == 0) {
358         return;
359     }
360 
361     int currentRow, currentCol, outputCol;
362     KTextEditor::Document *doc = view->document();
363     KTextEditor::Cursor cursor = view->cursorPosition();
364     currentRow = cursor.line();
365     currentCol = cursor.column();
366 
367     bool indent = !m_envAutoIndent.isEmpty();
368     if(indent && currentCol > 0) {
369         doc->insertText(KTextEditor::Cursor(currentRow, currentCol),"\n");
370         currentRow++;
371         currentCol = 0;
372     }
373 
374     bool ok1,ok2;
375     for(QStringList::Iterator it = envlist.begin(); it != envlist.end(); ++it) {
376         QStringList entry = (*it).split(',');
377         if(entry[0] == "document") {
378             break;
379         }
380 
381         int row = entry[1].toInt(&ok1);
382         int col = entry[2].toInt(&ok2);
383         if(!ok1 || !ok2) {
384             continue;
385         }
386 
387         outputCol = currentCol;
388         if(indent) {
389             QString whitespace = getWhiteSpace( doc->line(row).left(col) );
390             doc->insertText(KTextEditor::Cursor(currentRow, outputCol), whitespace);
391             outputCol += whitespace.length();
392         }
393         QString endtag = ( entry[0] == "\\[" ) ? "\\]\n" : "\\end{"+entry[0]+"}\n";
394         doc->insertText(KTextEditor::Cursor(currentRow, outputCol), endtag);
395         ++currentRow;
396     }
397 }
398 
399 //////////////////// mathgroup ////////////////////
400 
selectMathgroup(KTextEditor::View * view)401 void EditorExtension::selectMathgroup(KTextEditor::View *view)
402 {
403     view = determineView(view);
404     if(!view) {
405         return;
406     }
407 
408     KTextEditor::Range range = mathgroupRange(view);
409     if(range.isValid()) {
410         view->setSelection(range);
411     }
412 }
413 
deleteMathgroup(KTextEditor::View * view)414 void EditorExtension::deleteMathgroup(KTextEditor::View *view)
415 {
416     view = determineView(view);
417     if(!view) {
418         return;
419     }
420 
421     KTextEditor::Range range = mathgroupRange(view);
422     if(range.isValid()) {
423         deleteRange(range,view);
424     }
425 }
426 
hasMathgroup(KTextEditor::View * view)427 bool EditorExtension::hasMathgroup(KTextEditor::View *view)
428 {
429     // view will be checked in mathgroupRange()
430     KTextEditor::Range range = mathgroupRange(view);
431     return (range.isValid()) ? true : false;
432 }
433 
getMathgroupText(KTextEditor::View * view)434 QString EditorExtension::getMathgroupText(KTextEditor::View *view)
435 {
436     view = determineView(view);
437     if(!view) {
438         return QString();
439     }
440 
441     KTextEditor::Range range = mathgroupRange(view);
442     return (range.isValid()) ? view->document()->text(range) : QString();
443 }
444 
getMathgroupText(uint & row,uint & col,KTextEditor::View * view)445 QString EditorExtension::getMathgroupText(uint &row, uint &col, KTextEditor::View *view)
446 {
447     int row1, col1, row2, col2;
448 
449     view = determineView(view);
450     if(view && getMathgroup(view, row1, col1, row2, col2)) {
451         row = row1;
452         col = col1;
453         return view->document()->text(KTextEditor::Range(row1, col1, row2, col2));
454     }
455     else {
456         return QString();
457     }
458 }
459 
mathgroupRange(KTextEditor::View * view)460 KTextEditor::Range  EditorExtension::mathgroupRange(KTextEditor::View *view)
461 {
462     view = determineView(view);
463     if(!view) {
464         return KTextEditor::Range::invalid();
465     }
466 
467     int row1, col1, row2, col2;
468     if(getMathgroup(view, row1, col1, row2, col2)) {
469         return KTextEditor::Range(row1, col1, row2, col2);
470     }
471     else {
472         return KTextEditor::Range::invalid();
473     }
474 }
475 
getMathgroup(KTextEditor::View * view,int & row1,int & col1,int & row2,int & col2)476 bool EditorExtension::getMathgroup(KTextEditor::View *view, int &row1, int &col1, int &row2, int &col2)
477 {
478     int row, col, r, c;
479     MathData begin, end;
480 
481     KTextEditor::Document *doc = view->document();
482     KTextEditor::Cursor cursor = view->cursorPosition();
483     row = cursor.line();
484     col = cursor.column();
485 
486     QString textline = getTextLineReal(doc,row);
487 
488     // check for '\ensuremath{...}'
489     QString word;
490     int x1, x2;
491     if(getCurrentWord(doc, row, col, smTex, word, x1, x2) && word == "\\ensuremath") {
492         view->setCursorPosition(KTextEditor::Cursor(row, x2));
493     }
494 
495     BracketData open,close;
496     if(getTexgroup(false, open, close, view)) {
497         QString s = getTextLineReal(doc,open.row);
498         if(open.col >= 11 && s.mid(open.col - 11, 11) == "\\ensuremath") {
499             view->setCursorPosition(KTextEditor::Cursor(row, col));
500             row1 = open.row;
501             col1 = open.col-11;
502             row2 = close.row;
503             col2 = close.col;
504             return true;
505         }
506     }
507 
508     // do we need to restore the cursor position
509     view->setCursorPosition(KTextEditor::Cursor(row, col));
510 
511     // '$' is difficult, because it is used as opening and closing tag
512     int mode = 0;
513     if(textline[col] == '$') {
514         mode = 1;
515     }
516     else if(col > 0 && textline[col - 1] == '$') {
517         mode = 2;
518     }
519 
520     if(mode > 0) {
521         // first look, if this is a closing '$'
522         r = row;
523         c = (mode == 1) ? col : col - 1;
524         if(decreaseCursorPosition(doc, r, c) && findOpenMathTag(doc, r, c, begin)) {
525             if(begin.tag == mmMathDollar && (begin.numdollar & 1)) {
526                 row1 = begin.row;
527                 col1 = begin.col;
528                 row2 = row;
529                 col2 = (mode == 1) ? col + 1 : col;
530                 return true;
531             }
532         }
533 
534         // perhaps an opening '$'
535         r = row;
536         c = (mode == 1) ? col+1 : col;
537         if(findCloseMathTag(doc, r, c, end)) {
538             if(end.tag == mmMathDollar) {
539                 row1 = row;
540                 col1 = ( mode == 1 ) ? col : col-1;
541                 row2 = end.row;
542                 col2 = end.col + end.len;
543                 return true;
544             }
545         }
546 
547         // found no mathgroup with '$'
548         return false;
549     }
550 
551     // now let's search for all other math tags:
552     // if a mathgroup tag starts in the current column, we save this
553     // position and move the cursor one column to the right
554     bool openingtag = isOpeningMathTagPosition(doc, row, col, begin);
555     if(openingtag) {
556         // try to find the corresponding closing tag at the right
557         bool closetag = findCloseMathTag(doc, row, col + 1, end);
558         if(closetag && checkMathtags(begin, end)) {
559             row1 = begin.row;
560             col1 = begin.col;
561             row2 = end.row;
562             col2 = end.col + end.len;
563             return true;
564         }
565     }
566 
567     r = row;
568     c = col;
569     bool closingtag = isClosingMathTagPosition(doc, row, col, end);
570     if(closingtag) {
571         c = end.col;
572         if(!decreaseCursorPosition(doc, r, c)) {
573             return false;
574         }
575     }
576 
577     // now try to search to opening tag of the math group
578     if(!findOpenMathTag(doc, r, c, begin)) {
579         return false;
580     }
581 
582     if(begin.tag == mmMathDollar && !(begin.numdollar & 1)) {
583         //KILE_DEBUG_MAIN << "error: even number of '$' --> no math mode" ;
584         return false;
585     }
586 
587     // and now the closing tag
588     if(!findCloseMathTag(doc, r, c, end)) {
589         return false;
590     }
591 
592     // both tags were found, but they must be of the same type
593     if(checkMathtags(begin, end)) {
594         row1 = begin.row;
595         col1 = begin.col;
596         row2 = end.row;
597         col2 = end.col + end.len;
598         return true;
599     }
600     else {
601         return false;
602     }
603 }
604 
605 //////////////////// mathgroup tags ////////////////////
606 
checkMathtags(const MathData & begin,const MathData & end)607 bool EditorExtension::checkMathtags(const MathData &begin,const MathData &end)
608 {
609     // both tags were found, but they must be of the same type
610     if(begin.tag != end.tag) {
611         //KILE_DEBUG_MAIN << "error: opening and closing tag of mathmode don't match: " << begin.tag << " - " << end.tag;
612         return false;
613     }
614 
615     // and additionally: if it is a math env, both tags must have the same name
616     if(begin.tag == mmDisplaymathEnv && begin.envname != end.envname) {
617         //KILE_DEBUG_MAIN << "error: opening and closing env tags have different names: " << begin.envname << " - " << end.envname;
618         return false;
619     }
620 
621     return true;
622 }
623 
isOpeningMathTagPosition(KTextEditor::Document * doc,uint row,uint col,MathData & mathdata)624 bool EditorExtension::isOpeningMathTagPosition(KTextEditor::Document *doc, uint row, uint col, MathData &mathdata)
625 {
626     QString textline = getTextLineReal(doc,row);
627 
628     QRegExp reg("\\\\begin\\s*\\{([A-Za-z]+\\*?)\\}|\\\\\\[|\\\\\\(");
629     if((int)col != reg.indexIn(textline, col)) {
630         return false;
631     }
632 
633     QChar id = reg.cap(0)[1];
634     QString envname = reg.cap(1);
635 
636     mathdata.row = row;
637     mathdata.col = col;
638     mathdata.len = reg.cap(0).length();
639 
640     switch(id.unicode()) {
641     case 'b':
642         if(!(m_latexCommands->isMathEnv(envname) || envname=="math") || m_latexCommands->needsMathMode(envname)) {
643             return false;
644         }
645         mathdata.tag = ( envname=="math" ) ? mmMathEnv : mmDisplaymathEnv;
646         mathdata.envname = envname;
647         break;
648     case '[':
649         mathdata.tag = mmDisplaymathParen;
650         break;
651     case '(':
652         mathdata.tag = mmMathParen;
653         break;
654     }
655 
656     return true;
657 }
658 
isClosingMathTagPosition(KTextEditor::Document * doc,uint row,uint col,MathData & mathdata)659 bool EditorExtension::isClosingMathTagPosition(KTextEditor::Document *doc, uint row, uint col,MathData &mathdata)
660 {
661     QString textline = doc->line(row);
662 
663     QRegExp reg("\\\\end\\s*\\{([A-Za-z]+\\*?)\\}|\\\\\\]|\\\\\\)");
664     int pos = reg.lastIndexIn(textline, col);
665     if(pos < 0 || (int)col > pos + reg.matchedLength()) {
666         return false;
667     }
668 
669     QChar id = reg.cap(0)[1];
670     QString envname = reg.cap(1);
671 
672     mathdata.row = row;
673     mathdata.col = pos;
674     mathdata.len = reg.cap(0).length();
675 
676     switch(id.unicode()) {
677     case 'e':
678         if(!(m_latexCommands->isMathEnv(envname) || envname=="math") || m_latexCommands->needsMathMode(envname)) {
679             return false;
680         }
681         mathdata.tag = ( envname=="math" ) ? mmMathEnv : mmDisplaymathEnv;
682         mathdata.envname = envname;
683         break;
684     case ']':
685         mathdata.tag = mmDisplaymathParen;
686         break;
687     case ')':
688         mathdata.tag = mmMathParen;
689         break;
690     }
691 
692     return true;
693 }
694 
findOpenMathTag(KTextEditor::Document * doc,int row,int col,MathData & mathdata)695 bool EditorExtension::findOpenMathTag(KTextEditor::Document *doc, int row, int col, MathData &mathdata)
696 {
697     const QString regExpString = "\\$"
698                                  "|\\\\begin\\s*\\{([A-Za-z]+\\*?)\\}"
699                                  "|\\\\end\\s*\\{([A-Za-z]+\\*?)\\}"
700                                  "|\\\\\\[|\\\\\\]"
701                                  "|\\\\\\(|\\\\\\)";
702 
703     QRegExp reg(regExpString);
704     int lastrow = -1, lastcol = -1;
705     QString mathname;
706 
707     bool foundDollar = false;
708     uint numDollar = 0;
709 
710     QString textline = getTextLineReal(doc, row);
711     int column = col;
712 
713     bool continueSearch = true;
714     while(continueSearch) {
715         while((column = reg.lastIndexIn(textline, col)) != -1) {
716             col = column;
717 
718             mathdata.row = row;
719             mathdata.col = col;
720             mathdata.len = reg.cap(0).length();
721             mathname = reg.cap(0).left(2);
722 
723             // should be better called 'isValidChar()', because it checks for comments
724             // and escaped chars like backslash and dollar in '\\' and '\$'
725             if(mathname == "$") {
726                 // count and continue search
727                 ++numDollar;
728 
729                 // but remember the first dollar found backwards
730                 if(!foundDollar) {
731                     lastrow = row;
732                     lastcol = col;
733                     foundDollar = true;
734                 }
735             }
736             else if(mathname=="\\[" || mathname=="\\(") {
737                 // found start of mathmode
738                 if(numDollar == 0) {
739                     mathdata.tag = ( mathname == "\\[" ) ? mmDisplaymathParen : mmMathParen;
740                     mathdata.numdollar = 0;
741                     return true;
742                 }
743                 else {
744                     //KILE_DEBUG_MAIN << "error: dollar not allowed in \\[ or \\( mode";
745                     return false;
746                 }
747             }
748             else if(mathname=="\\]" || mathname=="\\)") {
749                 continueSearch = false;
750                 break;
751             }
752             else  if(mathname=="\\b") {
753                 // save name of environment
754                 QString envname = reg.cap(1);
755 
756                 // if we found the opening tag of a math env
757                 if(m_latexCommands->isMathEnv(envname) || envname=="math") {
758                     if(numDollar > 0) {
759                         //KILE_DEBUG_MAIN << "error: dollar not allowed in math env   numdollar=" << numDollar;
760                         return false;
761                     }
762 
763                     // if this is a math env with its own mathmode, we have found the starting position
764                     if(envname == "math") {
765                         mathdata.tag = mmMathEnv;
766                         mathdata.envname = envname;
767                         return true;
768                     }
769 
770                     if(!m_latexCommands->needsMathMode(envname)) {
771                         mathdata.tag = mmDisplaymathEnv;
772                         mathdata.envname = envname;
773                         return true;
774                     }
775                 }
776                 // no math env, we found the opening tag of a normal env
777                 else {
778                     continueSearch = false;
779                     break;
780                 }
781             }
782             else if(mathname == "\\e") {
783                 QString envname = reg.cap(2);
784 
785                 // if we found the closing tag of a math env
786                 if(m_latexCommands->isMathEnv(envname) || envname == "math") {
787                     // if this is a math env with its own mathmode
788                     if(!m_latexCommands->needsMathMode(envname) || envname == "math") {
789                         continueSearch = false;
790                         break;
791                     }
792 
793                     // if this is a math env which needs $..$
794                     if(m_latexCommands->isMathModeEnv(envname)) {
795                         if(numDollar >= 1) {
796                             --numDollar;
797                             continueSearch = false;
798                             break;
799                         }
800                         // else continue search
801                     }
802                 }
803                 // if we found the closing tag of a normal env
804                 else {
805                     continueSearch = false;
806                     break;
807                 }
808             }
809             else {
810                 //KILE_DEBUG_MAIN << "error: unknown match";
811                 return false;
812             }
813 
814             // continue search one column left of the last match (if this is possible)
815             if(col == 0) {
816                 break;
817             }
818 
819             --col;
820         }
821 
822         if(row > 0) {
823             textline = getTextLineReal(doc,--row);
824             col = textline.length();
825         }
826         else if(column == -1) {
827             continueSearch = false;
828             break;
829         }
830     }
831 
832     // nothing else found, so math mode starts a the last dollar (the first one found backwards)
833     mathdata.row = lastrow;
834     mathdata.col = lastcol;
835     mathdata.len = 1;
836     mathdata.numdollar = numDollar;
837 
838     mathdata.tag = (numDollar > 0) ? mmMathDollar : mmNoMathMode;
839 
840     return true;
841 }
842 
findCloseMathTag(KTextEditor::Document * doc,int row,int col,MathData & mathdata)843 bool EditorExtension::findCloseMathTag(KTextEditor::Document *doc, int row, int col, MathData &mathdata)
844 {
845     const QString regExpString = "\\$"
846                                  "|\\\\begin\\s*\\{([A-Za-z]+\\*?)\\}"
847                                  "|\\\\end\\s*\\{([A-Za-z]+\\*?)\\}"
848                                  "|\\\\\\[|\\\\\\]"
849                                  "|\\\\\\(|\\\\\\)";
850 
851 // +       int rowFound, colFound;
852 // +       QRegExp reg(regExpString);
853 // +       reg.setCaseSensitivity(Qt::CaseInsensitive);
854 // +       int lastMatch = 0;
855 
856     int rowFound, colFound;
857     QRegExp reg(regExpString);
858 
859     KTextEditor::Range searchRange = KTextEditor::Range(KTextEditor::Cursor(row, col), doc->documentEnd());
860 
861     while(true) {
862         QVector<KTextEditor::Range> foundRanges = doc->searchText(searchRange, regExpString, KTextEditor::Regex | KTextEditor::CaseInsensitive);
863         if(foundRanges.isEmpty() || (foundRanges.size() == 1 && !foundRanges.first().isValid())) {
864             break;
865         }
866 
867         //KILE_DEBUG_MAIN << "number of ranges " << foundRanges.count();
868         if(foundRanges.size() < 3) {
869             break;
870         }
871 
872         KTextEditor::Range range = foundRanges.first();
873         //KILE_DEBUG_MAIN << "found math tag: " << doc->text(range);
874         if(!range.isValid()) {
875             break;
876         }
877 
878         rowFound = range.start().line();
879         colFound = range.start().column();
880         QString textFound = doc->text(range);
881 
882         // should be better called 'isValidChar()', because it checks for comments
883         // and escaped chars like backslash and dollar in '\\' and '\$'
884         if(isValidBackslash(doc, rowFound, colFound)) {
885             QString mathname = textFound.left(2);
886 
887             // always remember behind the last match
888             mathdata.row = rowFound;
889             mathdata.col = colFound;
890             mathdata.len = textFound.length();
891 
892             if(mathname=="$") {
893                 mathdata.tag = mmMathDollar;
894                 return true;
895             }
896             else if(mathname=="\\]") {
897                 mathdata.tag = mmDisplaymathParen;
898                 return true;
899             }
900             else if(mathname=="\\)") {
901                 mathdata.tag = mmMathParen;
902                 return true;
903             }
904             else if(mathname=="\\[" || mathname=="\\(") {
905                 //KILE_DEBUG_MAIN << "error: current mathgroup was not closed";
906                 return false;
907             }
908             else if(mathname=="\\b") {
909                 QString envname = doc->text(foundRanges[1]);
910                 if(!(m_latexCommands->isMathEnv(envname) || envname=="math")) {
911                     //KILE_DEBUG_MAIN << "error: only math env are allowed in mathmode (found begin tag)";
912                     return false;
913                 }
914 
915                 if(!m_latexCommands->needsMathMode(envname) || envname=="math") {
916                     //KILE_DEBUG_MAIN << "error: mathenv with its own mathmode are not allowed in mathmode ";
917                     return false;
918                 }
919 
920                 // else continue search
921             }
922             else if(mathname == "\\e") {
923                 QString envname = doc->text(foundRanges[2]);
924                 if(!(m_latexCommands->isMathEnv(envname) || envname=="math")) {
925                     //KILE_DEBUG_MAIN << "error: only math env are allowed in mathmode (found end tag)";
926                     return false;
927                 }
928 
929                 if(envname == "math") {
930                     mathdata.tag = mmMathEnv;
931                     mathdata.envname = envname;
932                     return true;
933                 }
934 
935                 if(!m_latexCommands->needsMathMode(envname)) {
936                     mathdata.tag = mmDisplaymathEnv;
937                     mathdata.envname = envname;
938                     return true;
939                 }
940 
941                 // else continue search
942             }
943         }
944 
945         // go ahead
946         searchRange = KTextEditor::Range(foundRanges.first().end(), doc->documentEnd());
947     }
948 
949     //KILE_DEBUG_MAIN << "not found anything";
950     return false;
951 }
952 
953 
954 
955 //////////////////// insert newlines inside an environment ////////////////////
956 
957 // intelligent newlines: look for the last opened environment
958 // and decide what to insert
959 // or continue the comment
960 
insertIntelligentNewline(KTextEditor::View * view)961 void EditorExtension::insertIntelligentNewline(KTextEditor::View *view)
962 {
963     KILE_DEBUG_MAIN << view;
964 
965     view = determineView(view);
966 
967     if(!view) {
968         return;
969     }
970 
971     KTextEditor::Document* doc = view->document();
972 
973     if(!doc) {
974         return;
975     }
976 
977     QString name;
978     KTextEditor::Cursor cursor = view->cursorPosition();
979     int row = cursor.line();
980     int col = cursor.column();
981 
982     QString newLineAndIndentationString = '\n' + extractIndentationString(view, row);
983 
984     if(isCommentPosition(doc, row, col)) {
985         KILE_DEBUG_MAIN << "found comment";
986         view->insertText(newLineAndIndentationString + "% ");
987         moveCursorToLastPositionInCurrentLine(view);
988         return;
989     }
990     else if(findOpenedEnvironment(row, col, name, view)) {
991         if(m_latexCommands->isListEnv(name)) {
992             if ( name == "description" ) {
993                 view->insertText(newLineAndIndentationString + "\\item[]");
994             }
995             else {
996                 view->insertText(newLineAndIndentationString + "\\item ");
997             }
998             moveCursorToLastPositionInCurrentLine(view);
999             return;
1000         }
1001         else if(m_latexCommands->isTabularEnv(name) || m_latexCommands->isMathEnv(name)) {
1002             view->insertText(newLineAndIndentationString + "\\\\");
1003             moveCursorToLastPositionInCurrentLine(view);
1004             return;
1005         }
1006     }
1007     // - no comment position
1008     // - found no opened environment
1009     // - unknown environment
1010     // - finish tabular or math environment
1011     view->insertText(newLineAndIndentationString);
1012     moveCursorToLastPositionInCurrentLine(view);
1013 }
1014 
findOpenedEnvironment(int & row,int & col,QString & envname,KTextEditor::View * view)1015 bool EditorExtension::findOpenedEnvironment(int &row, int &col, QString &envname, KTextEditor::View *view)
1016 {
1017     view = determineView(view);
1018     if(!view) {
1019         return false;
1020     }
1021 
1022     // get current cursor position
1023     KTextEditor::Document *doc = view->document();
1024     KTextEditor::Cursor cursor = view->cursorPosition();
1025     row = cursor.line();
1026     col = cursor.column();
1027 
1028     EnvData env;
1029     int startrow = row;
1030     int startcol = col;
1031 
1032     //KILE_DEBUG_MAIN << "   close - start ";
1033     // accept a starting place outside an environment
1034     bool env_position = isEnvironmentPosition(doc, row, col, env);
1035 
1036     // We can also accept a column, if we are on the left side of an environment.
1037     // But we should decrease the current cursor position for the search.
1038     if(env_position && env.cpos != EnvInside) {
1039         if(env.cpos == EnvLeft && !decreaseCursorPosition(doc, startrow, startcol)) {
1040             return false;
1041         }
1042         env_position = false;
1043     }
1044 
1045     if(!env_position && findEnvironmentTag(doc, startrow, startcol, env, true)) {
1046         //KILE_DEBUG_MAIN << "   close - found begin env at:  " << env.row << "/" << env.col << " " << env.name;
1047         row = env.row;
1048         col = env.col;
1049         envname = env.name;
1050         return true;
1051     }
1052     else {
1053         return false;
1054     }
1055 }
1056 
findOpenedEnvironmentList(KTextEditor::View * view,bool position)1057 QStringList EditorExtension::findOpenedEnvironmentList(KTextEditor::View *view, bool position)
1058 {
1059     QStringList envlist;
1060 
1061     view = determineView(view);
1062     if(view) {
1063         int currentRow, currentCol;
1064         KTextEditor::Document *doc = view->document();
1065         KTextEditor::Cursor cursor = view->cursorPosition();
1066         currentRow = cursor.line();
1067         currentCol = cursor.column();
1068 
1069 
1070         int row = currentRow;
1071         int col = currentCol;
1072         EnvData env;
1073 
1074         // check the starting position
1075         bool env_position = isEnvironmentPosition(doc, row, col, env);
1076         if(env_position) {
1077             // we are inside an environment tag: bad to complete
1078             if(env.cpos == EnvInside) {
1079                 return envlist;
1080             }
1081             // we are left of an environment tag: go one position to the left
1082             if(env.cpos == EnvLeft) {
1083                 if (!decreaseCursorPosition(doc, row, col)) {
1084                     return envlist;
1085                 }
1086             }
1087         }
1088 
1089         while (findEnvironmentTag(doc, row, col, env, true)) {
1090             row = env.row;
1091             col = env.col;
1092 
1093             if(position) {
1094                 envlist << env.name + QString(",%1,%2").arg(row).arg(col);
1095             }
1096             else {
1097                 envlist << env.name;
1098             }
1099 
1100             if(col == 0) {
1101                 if (!decreaseCursorPosition(doc, row, col)) {
1102                     break;
1103                 }
1104             }
1105             view->setCursorPosition(KTextEditor::Cursor(row, col));
1106         }
1107 
1108         // reset cursor original position
1109         view->setCursorPosition(KTextEditor::Cursor(currentRow, currentCol));
1110     }
1111 
1112     return envlist;
1113 }
1114 
1115 //////////////////// select an environment  ////////////////////
1116 
selectEnvironment(bool inside,KTextEditor::View * view)1117 void EditorExtension::selectEnvironment(bool inside, KTextEditor::View *view)
1118 {
1119     view = determineView(view);
1120     if(!view) {
1121         return;
1122     }
1123 
1124     if (!view->selection() || !expandSelectionEnvironment(inside,view)) {
1125         KTextEditor::Range range = environmentRange(inside,view);
1126         if(range.isValid()) {
1127             view->setSelection(range);
1128         }
1129     }
1130 }
1131 
deleteEnvironment(bool inside,KTextEditor::View * view)1132 void EditorExtension::deleteEnvironment(bool inside, KTextEditor::View *view)
1133 {
1134     view = determineView(view);
1135     if(!view) {
1136         return;
1137     }
1138 
1139     KTextEditor::Range range = environmentRange(inside,view);
1140     if(range.isValid()) {
1141         deleteRange(range,view);
1142     }
1143 }
1144 
deleteRange(KTextEditor::Range & range,KTextEditor::View * view)1145 void EditorExtension::deleteRange(KTextEditor::Range &range, KTextEditor::View *view)
1146 {
1147     view->removeSelection();
1148     view->document()->removeText(range);
1149     view->setCursorPosition(range.start());
1150 }
1151 
1152 // calculate start and end of an environment
1153 
getEnvironment(bool inside,EnvData & envbegin,EnvData & envend,KTextEditor::View * view)1154 bool EditorExtension::getEnvironment(bool inside, EnvData &envbegin, EnvData &envend, KTextEditor::View *view)
1155 {
1156     view = determineView(view);
1157     if(!view) {
1158         return false;
1159     }
1160 
1161     int row, col;
1162 
1163     KTextEditor::Document *doc = view->document();
1164     KTextEditor::Cursor cursor = view->cursorPosition();
1165     row = cursor.line();
1166     col = cursor.column();
1167     if(!findBeginEnvironment(doc, row, col, envbegin)) {
1168         return false;
1169     }
1170     if(!findEndEnvironment(doc, row, col, envend)) {
1171         return false;
1172     }
1173 
1174     if(inside) {
1175         envbegin.col += envbegin.len;
1176     }
1177     else {
1178         envend.col += envend.len;
1179     }
1180 
1181     return true;
1182 }
1183 
environmentRange(bool inside,KTextEditor::View * view)1184 KTextEditor::Range EditorExtension::environmentRange(bool inside, KTextEditor::View *view)
1185 {
1186     // view will be checked in getEnvironment()
1187     EnvData envbegin, envend;
1188     return (getEnvironment(inside, envbegin, envend, view))
1189            ? KTextEditor::Range(envbegin.row, envbegin.col, envend.row, envend.col)
1190            : KTextEditor::Range::invalid();
1191 }
1192 
environmentText(bool inside,KTextEditor::View * view)1193 QString EditorExtension::environmentText(bool inside, KTextEditor::View *view)
1194 {
1195     view = determineView(view);
1196     if(!view) {
1197         return QString();
1198     }
1199 
1200     KTextEditor::Range range = environmentRange(inside,view);
1201     return (range.isValid()) ? view->document()->text(range) : QString();
1202 }
1203 
environmentName(KTextEditor::View * view)1204 QString EditorExtension::environmentName(KTextEditor::View *view)
1205 {
1206     // view will be checked in getEnvironment()
1207     EnvData envbegin, envend;
1208     return (getEnvironment(false, envbegin, envend, view)) ? envbegin.name : QString();
1209 }
1210 
1211 // determine text, startrow and startcol of current environment
1212 
getEnvironmentText(int & row,int & col,QString & name,KTextEditor::View * view)1213 QString EditorExtension::getEnvironmentText(int &row, int &col, QString &name, KTextEditor::View *view)
1214 {
1215     view = determineView(view);
1216     if(!view) {
1217         return QString();
1218     }
1219 
1220     EnvData envbegin, envend;
1221 
1222     if(getEnvironment(false, envbegin, envend, view) && envbegin.name != "document") {
1223         row = envbegin.row;
1224         col = envbegin.col;
1225         name = envbegin.name;
1226         return view->document()->text(KTextEditor::Range(envbegin.row, envbegin.col, envend.row, envend.col));
1227     }
1228     else {
1229         return QString();
1230     }
1231 }
1232 
hasEnvironment(KTextEditor::View * view)1233 bool EditorExtension::hasEnvironment(KTextEditor::View *view)
1234 {
1235     view = determineView(view);
1236     if(!view) {
1237         return false;
1238     }
1239 
1240     EnvData envbegin,envend;
1241     return (getEnvironment(false, envbegin, envend, view) && envbegin.name != "document");
1242 }
1243 
1244 // when an environment is selected (inside or outside),
1245 // the selection is expanded to the surrounding environment
1246 
expandSelectionEnvironment(bool inside,KTextEditor::View * view)1247 bool EditorExtension::expandSelectionEnvironment(bool inside, KTextEditor::View *view)
1248 {
1249     KTextEditor::Document *doc = view->document();
1250     if (!view->selection()) {
1251         return false;
1252     }
1253 
1254     // get current position
1255     int row, col;
1256     KTextEditor::Cursor cursor = view->cursorPosition();
1257     row = cursor.line();
1258     col = cursor.column();
1259 
1260     // get current selection
1261     KTextEditor::Range selectionRange = view->selectionRange();
1262     int row1 = selectionRange.start().line();
1263     int col1 = selectionRange.start().column();
1264     int row2 = selectionRange.end().line();
1265     int col2 = selectionRange.end().column();
1266 
1267     // determine current environment outside
1268     EnvData oenvbegin,oenvend;
1269     if(!getEnvironment(false, oenvbegin, oenvend, view)) {
1270         return false;
1271     }
1272 
1273     bool newselection = false;
1274     // first look, if this environment is selected outside
1275     if(row1 == oenvbegin.row && col1 == oenvbegin.col && row2 == oenvend.row && col2 == oenvend.col) {
1276 
1277         if(!decreaseCursorPosition(doc, oenvbegin.row, oenvbegin.col) ) {
1278             return newselection;
1279         }
1280         view->setCursorPosition(KTextEditor::Cursor(oenvbegin.row, oenvbegin.col));
1281         // search the surrounding environment and select it
1282         if(getEnvironment(inside, oenvbegin, oenvend, view)) {
1283             view->setSelection(KTextEditor::Range(oenvbegin.row, oenvbegin.col, oenvend.row, oenvend.col));
1284             newselection = true;
1285 
1286         }
1287     }
1288     else {
1289         // then determine current environment inside
1290         EnvData ienvbegin, ienvend;
1291         getEnvironment(true, ienvbegin, ienvend, view);
1292         // and look, if this environment is selected inside
1293         if(row1 == ienvbegin.row && col1 == ienvbegin.col && row2 == ienvend.row && col2 == ienvend.col) {
1294             if(!decreaseCursorPosition(doc, oenvbegin.row, oenvbegin.col) ) {
1295                 return newselection;
1296             }
1297             view->setCursorPosition(KTextEditor::Cursor(oenvbegin.row, oenvbegin.col));
1298             // search the surrounding environment and select it
1299             if(getEnvironment(inside, ienvbegin, ienvend, view)) {
1300                 view->setSelection(KTextEditor::Range(ienvbegin.row, ienvbegin.col, ienvend.row, ienvend.col));
1301                 newselection = true;
1302             }
1303 
1304         }
1305     }
1306 
1307     // restore old cursor position
1308     view->setCursorPosition(KTextEditor::Cursor(row, col));
1309     return newselection;
1310 }
1311 
1312 //////////////////// search for \begin{env}  ////////////////////
1313 
1314 // Find the last \begin{env} tag. If the current cursor is over
1315 //  - \begin{env} tag: we will stop immediately
1316 //  - \end{env} tag: we will start before this tag
1317 
findBeginEnvironment(KTextEditor::Document * doc,int row,int col,EnvData & env)1318 bool EditorExtension::findBeginEnvironment(KTextEditor::Document *doc, int row, int col, EnvData &env)
1319 {
1320     // KILE_DEBUG_MAIN << "   find begin:  ";
1321     if(isEnvironmentPosition(doc, row, col, env)) {
1322         // already found position?
1323         //KILE_DEBUG_MAIN << "   found env at:  " << env.row << "/" << env.col << " " << env.name;
1324         if(env.tag == EnvBegin) {
1325             //KILE_DEBUG_MAIN << "   is begin env at:  " << env.row << "/" << env.col << " " << env.name;
1326             return true;
1327         }
1328 
1329         // go one position back
1330         //KILE_DEBUG_MAIN << "   is end env at:  " << env.row << "/" << env.col << " " << env.name;
1331         row = env.row;
1332         col = env.col;
1333         if(!decreaseCursorPosition(doc, row, col)) {
1334             return false;
1335         }
1336     }
1337 
1338     // looking back for last environment
1339     //KILE_DEBUG_MAIN << "   looking back from pos:  " << row << "/" << col << " " << env.name;
1340     return findEnvironmentTag(doc, row, col, env, true);
1341 }
1342 
1343 //////////////////// search for \end{env}  ////////////////////
1344 
1345 // Find the last \end{env} tag. If the current cursor is over
1346 //  - \end{env} tag: we will stop immediately
1347 //  - \begin{env} tag: we will start behind this tag
1348 
findEndEnvironment(KTextEditor::Document * doc,int row,int col,EnvData & env)1349 bool EditorExtension::findEndEnvironment(KTextEditor::Document *doc, int row, int col, EnvData &env)
1350 {
1351     if(isEnvironmentPosition(doc, row, col, env)) {
1352         // already found position?
1353         if(env.tag == EnvEnd ) {
1354             return true;
1355         }
1356 
1357         // go one position forward
1358         row = env.row;
1359         col = env.col + 1;
1360     }
1361 
1362     // looking forward for the next environment
1363     return findEnvironmentTag(doc, row, col, env, false);
1364 }
1365 
1366 //////////////////// search for an environment tag ////////////////////
1367 // find the last/next non-nested environment tag
findEnvironmentTag(KTextEditor::Document * doc,int row,int col,EnvData & env,bool backwards)1368 bool EditorExtension::findEnvironmentTag(KTextEditor::Document *doc, int row, int col, EnvData &env, bool backwards)
1369 {
1370     unsigned int envcount = 0;
1371 
1372     KTextEditor::Range searchRange;
1373     if(backwards) {
1374         searchRange = KTextEditor::Range(KTextEditor::Cursor(0, 0), KTextEditor::Cursor(row, col));
1375     }
1376     else {
1377         searchRange = KTextEditor::Range(KTextEditor::Cursor(row, col), doc->documentEnd());
1378     }
1379 
1380     KTextEditor::SearchOptions searchOptions = (backwards) ? KTextEditor::Regex | KTextEditor::Backwards : KTextEditor::Regex;
1381 
1382     while(true) {
1383         QVector<KTextEditor::Range> foundRanges = doc->searchText(searchRange, m_reg.pattern(), searchOptions);
1384         if(foundRanges.isEmpty() || (foundRanges.size() == 1 && !foundRanges.first().isValid())) {
1385             break;
1386         }
1387 
1388         //KILE_DEBUG_MAIN << "number of ranges " << foundRanges.count();
1389 
1390         EnvTag wrong_env = (backwards) ? EnvEnd : EnvBegin;
1391 
1392         if(foundRanges.size() < 5) {
1393             break;
1394         }
1395 
1396         KTextEditor::Range range = foundRanges.first();
1397 
1398         if(!range.isValid()) {
1399             //KILE_DEBUG_MAIN << "invalid range found";
1400             break;
1401         }
1402         env.row = range.start().line();
1403         env.col = range.start().column();
1404         env.len = doc->text(range).length();
1405 
1406         if(isValidBackslash(doc, env.row, env.col)) {
1407             // index 0 is the fullmatch, 1 first cap and so on
1408             QString cap2 = (foundRanges[2].isValid() ? doc->text(foundRanges[2]) : "");
1409             QString cap3 = (foundRanges[3].isValid() ? doc->text(foundRanges[3]) : "");
1410             QString cap4 = (foundRanges[4].isValid() ? doc->text(foundRanges[4]) : "");
1411             EnvTag found_env = (cap2 == "begin" || cap4 == "\\[") ? EnvBegin : EnvEnd;
1412             if(found_env == wrong_env) {
1413                 ++envcount;
1414             }
1415             else {
1416                 if(envcount > 0) {
1417                     --envcount;
1418                 }
1419                 else {
1420                     if(found_env == EnvBegin) {
1421                         env.name = (cap2 == "begin") ? cap3 : "\\[";
1422                     }
1423                     else {
1424                         env.name = (cap2 == "end") ? cap3 : "\\]";
1425                     }
1426                     env.tag = found_env;
1427                     //KILE_DEBUG_MAIN << "found " << env.name;
1428                     return true;
1429                 }
1430             }
1431         }
1432 
1433         // finally, prepare the range for the next search
1434         if(backwards) {
1435             searchRange = KTextEditor::Range(KTextEditor::Cursor(0, 0), foundRanges.first().start());
1436         }
1437         else {
1438             searchRange = KTextEditor::Range(foundRanges.first().end(), doc->documentEnd());
1439         }
1440     }
1441 
1442     //KILE_DEBUG_MAIN << "not found anything";
1443     return false;
1444 }
1445 
1446 //////////////////// check for an environment position ////////////////////
1447 
1448 // Check if the current position belongs to an environment. The result is set
1449 // to the beginning backslash of the environment tag. The same algorithms as
1450 // matching brackets is used.
1451 //
1452 // insert mode:    if there is a full tag on the left, always take it
1453 //                 if not, look to the right
1454 // overwrite mode: always take the tag, which begins at the cursor position
1455 //
1456 // test it with {a}{{b}}{c}
1457 
isEnvironmentPosition(KTextEditor::Document * doc,int row,int col,EnvData & env)1458 bool EditorExtension::isEnvironmentPosition(KTextEditor::Document *doc, int row, int col, EnvData &env)
1459 {
1460     // get real textline without comments, quoted characters and pairs of backslashes
1461     QString textline = getTextLineReal(doc, row);
1462 
1463     if(col > textline.length()) {
1464         return false;
1465     }
1466 
1467     bool left = false;
1468 
1469     //KILE_DEBUG_MAIN << "col=" << col;
1470 
1471     // check if there is a match in this line from the current position to the left
1472     int startcol = (textline[col] == '\\') ? col - 1 : col;
1473     if(startcol >= 1) {
1474         //KILE_DEBUG_MAIN << "search to the left ";
1475         int pos = textline.lastIndexOf(m_reg, startcol);
1476         env.len = m_reg.matchedLength();
1477         if(pos != -1 && pos < col && col <= pos + env.len) {
1478             //KILE_DEBUG_MAIN << "search to the left: found";
1479             env.row = row;
1480             env.col = pos;
1481             QChar ch = textline.at(pos + 1);
1482             if(ch=='b' || ch=='e') {
1483                 env.tag = (ch == 'b') ? EnvBegin : EnvEnd;
1484                 env.name = m_reg.cap(3);
1485             }
1486             else {
1487                 env.tag = (ch == '[') ? EnvBegin : EnvEnd;
1488                 env.name = m_reg.cap(4);
1489             }
1490 
1491             if ( !m_overwritemode || (m_overwritemode && col<pos+env.len) ) {
1492                 // insert mode:    position is inside the tag or behind the tag, which also belongs to the tag
1493                 // overwrit emode: position is inside the tag) {
1494                 //KILE_DEBUG_MAIN << "search to the left: stop";
1495                 return true;
1496             }
1497             // overwritemode: position is behind the tag
1498             left = true;
1499             //KILE_DEBUG_MAIN << "search to the left: left=true, but also look to the right";
1500         }
1501     }
1502 
1503     // check if there is a match in this line from the current position to the right
1504     //KILE_DEBUG_MAIN << "search to the right " ;
1505     if (textline[col] == '\\' && col == textline.indexOf(m_reg, col)) {
1506         //KILE_DEBUG_MAIN << "search to the right: found";
1507         env.row = row;
1508         env.col = col;
1509         env.len = m_reg.matchedLength();
1510         QChar ch = textline.at(col+1);
1511         if(ch == 'b' || ch == 'e') { // found "\begin" or "\end"
1512             env.tag = ( ch == 'b' ) ? EnvBegin : EnvEnd;
1513             env.name = m_reg.cap(3);
1514         }
1515         else { // found "\[" or "\\]"
1516             env.tag = (ch == '[') ? EnvBegin : EnvEnd;
1517             env.name = m_reg.cap(4);
1518         }
1519         //KILE_DEBUG_MAIN << "search to the right: stop";
1520         return true;
1521     }
1522 
1523     return left;
1524 }
1525 
1526 //////////////////// check for a comment ////////////////////
1527 
1528 // check if the current position is within a comment
1529 
isCommentPosition(KTextEditor::Document * doc,int row,int col)1530 bool EditorExtension::isCommentPosition(KTextEditor::Document *doc, int row, int col)
1531 {
1532     QString textline = doc->line(row);
1533 
1534     bool backslash = false;
1535     for(int i = 0; i < col; ++i) {
1536         if(textline[i] == '%') {
1537             if(!backslash) { // found a comment sign
1538                 return true;
1539             }
1540             else {
1541                 backslash = false;
1542             }
1543         }
1544         else if(textline[i] == '\\') { // count number of backslashes
1545             backslash = !backslash;
1546         }
1547         else {
1548             backslash = false;               // no backslash
1549         }
1550     }
1551 
1552     return false;
1553 }
1554 
1555 // check if the character at text[col] is a valid backslash:
1556 //  - there is no comment sign in this line before
1557 //  - there is not a odd number of backslashes directly before
1558 
isValidBackslash(KTextEditor::Document * doc,int row,int col)1559 bool EditorExtension::isValidBackslash(KTextEditor::Document *doc, int row, int col)
1560 {
1561     QString textline = doc->line(row);
1562 
1563     bool backslash = false;
1564     for(int i = 0; i < col; ++i) {
1565         if(textline[i] == '%') {
1566             if(!backslash) {
1567                 return false;                 // found a comment sign
1568             }
1569             else {
1570                 backslash = false;
1571             }
1572         }
1573         else if(textline[i] == '\\') {  // count number of backslashes
1574             backslash = !backslash;
1575         }
1576         else {
1577             backslash = false;               // no backslash
1578         }
1579     }
1580 
1581     return !backslash;
1582 }
1583 
1584 //////////////////// goto next bullet ////////////////////
1585 
gotoBullet(bool backwards,KTextEditor::View * view)1586 void EditorExtension::gotoBullet(bool backwards, KTextEditor::View *view)
1587 {
1588     view = determineView(view);
1589     if(!view) {
1590         return;
1591     }
1592 
1593     // get current position
1594     KTextEditor::Document *doc = view->document();
1595 
1596     KTextEditor::Cursor cursor = view->cursorPosition();
1597 
1598     KTextEditor::SearchOptions searchOptions = (backwards) ? KTextEditor::Backwards : KTextEditor::Default;
1599 
1600     KTextEditor::Range searchRange;
1601     if(backwards) {
1602         searchRange = KTextEditor::Range(KTextEditor::Cursor(0, 0), cursor);
1603     }
1604     else {
1605         const KTextEditor::Cursor nextCursorPosition(cursor.line(), cursor.column() + 1);
1606         if((doc->characterAt(cursor) == s_bullet_char)                                      // we are already at a bullet
1607                 && view->selection()
1608                 && view->selectionRange() == KTextEditor::Range(cursor, nextCursorPosition)) { // which has been 'highlighted'
1609             cursor = nextCursorPosition; // search for the next bullet
1610         }
1611         searchRange = KTextEditor::Range(cursor, doc->documentEnd());
1612     }
1613 
1614     QVector<KTextEditor::Range> foundRanges = doc->searchText(searchRange, s_bullet, searchOptions);
1615     if(foundRanges.size() >= 1) {
1616         KTextEditor::Range range = foundRanges.first();
1617         if(range.isValid()) {
1618             int line = range.start().line();
1619             int column = range.start().column();
1620             view->setCursorPosition(KTextEditor::Cursor(line, column));
1621             view->setSelection(KTextEditor::Range(line, column, line, column + 1));
1622         }
1623     }
1624 }
1625 
1626 //////////////////// increase/decrease cursor position ////////////////////
1627 
moveCursorRight(KTextEditor::View * view)1628 bool EditorExtension::moveCursorRight(KTextEditor::View *view)
1629 {
1630     return moveCursor(view, MoveCursorRight);
1631 }
1632 
moveCursorLeft(KTextEditor::View * view)1633 bool EditorExtension::moveCursorLeft(KTextEditor::View *view)
1634 {
1635     return moveCursor(view, MoveCursorLeft);
1636 }
1637 
moveCursorUp(KTextEditor::View * view)1638 bool EditorExtension::moveCursorUp(KTextEditor::View *view)
1639 {
1640     return moveCursor(view, MoveCursorUp);
1641 }
1642 
moveCursorDown(KTextEditor::View * view)1643 bool EditorExtension::moveCursorDown(KTextEditor::View *view)
1644 {
1645     return moveCursor(view, MoveCursorDown);
1646 }
1647 
moveCursor(KTextEditor::View * view,CursorMove direction)1648 bool EditorExtension::moveCursor(KTextEditor::View *view, CursorMove direction)
1649 {
1650     view = determineView(view);
1651     if(!view) {
1652         return false;
1653     }
1654 
1655     KTextEditor::Document *doc = view->document();
1656 
1657     KTextEditor::Cursor cursor = view->cursorPosition();
1658     int row = cursor.line();
1659     int col = cursor.column();
1660 
1661     bool ok = false;
1662     switch (direction)  {
1663     case MoveCursorLeft:
1664         ok = decreaseCursorPosition(doc,row,col);
1665         break;
1666     case MoveCursorRight:
1667         ok = increaseCursorPosition(doc,row,col);
1668         break;
1669     case MoveCursorUp:
1670         if(row > 0) {
1671             row--;
1672             ok = true;
1673         }
1674         break;
1675     case MoveCursorDown:
1676         if(row < doc->lines() - 1) {
1677             row++;
1678             ok = true;
1679         }
1680         break;
1681     }
1682 
1683     if(ok) {
1684         return view->setCursorPosition(KTextEditor::Cursor(row,col));
1685     }
1686     else {
1687         return false;
1688     }
1689 }
1690 
increaseCursorPosition(KTextEditor::Document * doc,int & row,int & col)1691 bool EditorExtension::increaseCursorPosition(KTextEditor::Document *doc, int &row, int &col)
1692 {
1693     bool ok = true;
1694 
1695     if(col < doc->lineLength(row) - 1) {
1696         ++col;
1697     }
1698     else if(row < doc->lines() - 1) {
1699         ++row;
1700         col = 0;
1701     }
1702     else {
1703         ok = false;
1704     }
1705 
1706     return ok;
1707 }
1708 
decreaseCursorPosition(KTextEditor::Document * doc,int & row,int & col)1709 bool EditorExtension::decreaseCursorPosition(KTextEditor::Document *doc, int &row, int &col)
1710 {
1711     bool ok = true;
1712 
1713     if(col > 0) {
1714         --col;
1715     }
1716     else if(row > 0) {
1717         --row;
1718         col = doc->lineLength(row);
1719     }
1720     else {
1721         ok = false;
1722     }
1723 
1724     return ok;
1725 }
1726 
1727 //////////////////// texgroups ////////////////////
1728 
1729 // goto the next non-nested bracket
1730 
gotoTexgroup(bool backwards,KTextEditor::View * view)1731 void EditorExtension::gotoTexgroup(bool backwards, KTextEditor::View *view)
1732 {
1733     view = determineView(view);
1734     if(!view) return;
1735 
1736     uint row,col;
1737     bool found;
1738     BracketData bracket;
1739 
1740     // get current position
1741     KTextEditor::Document *doc = view->document();
1742     KTextEditor::Cursor cursor = view->cursorPosition();
1743     row = cursor.line();
1744     col = cursor.column();
1745     m_overwritemode = (view->viewMode() == KTextEditor::View::NormalModeOverwrite);
1746 
1747     // start searching
1748     if(backwards) {
1749         found = findOpenBracket(doc, row, col, bracket);
1750     }
1751     else {
1752         found = findCloseBracket(doc, row, col, bracket);
1753         // go behind the bracket
1754         if(!m_overwritemode) {
1755             ++bracket.col;
1756         }
1757     }
1758 
1759     if(found) {
1760         view->setCursorPosition(KTextEditor::Cursor(bracket.row, bracket.col));
1761     }
1762 }
1763 
1764 // match the opposite bracket
1765 
matchTexgroup(KTextEditor::View * view)1766 void EditorExtension::matchTexgroup(KTextEditor::View *view)
1767 {
1768     view = determineView(view);
1769     if(!view) {
1770         return;
1771     }
1772 
1773     int row, col;
1774     BracketData bracket;
1775 
1776     // get current position
1777     KTextEditor::Document *doc = view->document();
1778     KTextEditor::Cursor cursor = view->cursorPosition();
1779     row = cursor.line();
1780     col = cursor.column();
1781     m_overwritemode = (view->viewMode() == KTextEditor::View::NormalModeOverwrite);
1782 
1783     // this operation is only allowed at a bracket position
1784     if(!isBracketPosition(doc, row, col, bracket)) {
1785         return;
1786     }
1787 
1788     // start searching
1789     bool found = false;
1790     if(bracket.open) {
1791         found = findCloseBracketTag(doc, bracket.row, bracket.col + 1, bracket);
1792         // go behind the bracket
1793         if(!m_overwritemode) {
1794             ++bracket.col;
1795         }
1796     }
1797     else {
1798         if(!decreaseCursorPosition(doc, bracket.row, bracket.col)) {
1799             return;
1800         }
1801         found = findOpenBracketTag(doc, bracket.row, bracket.col, bracket);
1802     }
1803 
1804     if(found) {
1805         view->setCursorPosition(KTextEditor::Cursor(bracket.row, bracket.col));
1806     }
1807 }
1808 
1809 //////////////////// close an open texgroup  ////////////////////
1810 
1811 // search for the last opened texgroup and close it
1812 
closeTexgroup(KTextEditor::View * view)1813 void EditorExtension::closeTexgroup(KTextEditor::View *view)
1814 {
1815     view = determineView(view);
1816     if(!view) {
1817         return;
1818     }
1819 
1820     int row, col;
1821     BracketData bracket;
1822 
1823     KTextEditor::Document *doc = view->document();
1824     KTextEditor::Cursor cursor = view->cursorPosition();
1825     row = cursor.line();
1826     col = cursor.column();
1827 
1828     int rowtemp = row;
1829     int coltemp = col;
1830     if(!decreaseCursorPosition(doc, rowtemp, coltemp)) {
1831         return;
1832     }
1833 
1834     if(findOpenBracketTag(doc, rowtemp, coltemp, bracket)) {
1835         doc->insertText(KTextEditor::Cursor(row, col), "}");
1836         view->setCursorPosition(KTextEditor::Cursor(row, col + 1));
1837     }
1838 }
1839 
1840 //////////////////// select a texgroup  ////////////////////
1841 
selectTexgroup(bool inside,KTextEditor::View * view)1842 void EditorExtension::selectTexgroup(bool inside, KTextEditor::View *view)
1843 {
1844     view = determineView(view);
1845     if(!view) {
1846         return;
1847     }
1848 
1849     KTextEditor::Range range = texgroupRange(inside,view);
1850     if(range.isValid()) {
1851         view->setSelection(range);
1852     }
1853 }
1854 
deleteTexgroup(bool inside,KTextEditor::View * view)1855 void EditorExtension::deleteTexgroup(bool inside, KTextEditor::View *view)
1856 {
1857     view = determineView(view);
1858     if(!view) {
1859         return;
1860     }
1861 
1862     KTextEditor::Range range =texgroupRange(inside,view);
1863     if(range.isValid()) {
1864         deleteRange(range, view);
1865     }
1866 }
1867 
1868 // calculate start and end of a Texgroup
1869 
texgroupRange(bool inside,KTextEditor::View * view)1870 KTextEditor::Range EditorExtension::texgroupRange(bool inside, KTextEditor::View *view)
1871 {
1872     view = determineView(view);
1873     if(!view) {
1874         return KTextEditor::Range::invalid();
1875     }
1876 
1877     BracketData open, close;
1878     if(getTexgroup(inside, open, close, view)) {
1879         return KTextEditor::Range(open.row, open.col, close.row, close.col);
1880     }
1881     else {
1882         return KTextEditor::Range::invalid();
1883     }
1884 }
1885 
hasTexgroup(KTextEditor::View * view)1886 bool EditorExtension::hasTexgroup(KTextEditor::View *view)
1887 {
1888     // view will be checked in texgroupRange()
1889     KTextEditor::Range range = texgroupRange(true, view);
1890     return (range.isValid()) ? true : false;
1891 }
1892 
getTexgroupText(bool inside,KTextEditor::View * view)1893 QString EditorExtension::getTexgroupText(bool inside, KTextEditor::View *view)
1894 {
1895     view = determineView(view);
1896     if(!view) {
1897         return QString();
1898     }
1899 
1900     KTextEditor::Range range = texgroupRange(inside,view);
1901     return (range.isValid()) ? view->document()->text(range) : QString();
1902 }
1903 
getTexgroup(bool inside,BracketData & open,BracketData & close,KTextEditor::View * view)1904 bool EditorExtension::getTexgroup(bool inside, BracketData &open, BracketData &close, KTextEditor::View *view)
1905 {
1906     view = determineView(view);
1907     if(!view) {
1908         return false;
1909     }
1910 
1911     int row, col;
1912 
1913     KTextEditor::Document *doc = view->document();
1914     KTextEditor::Cursor cursor = view->cursorPosition();
1915     row = cursor.line();
1916     col = cursor.column();
1917 
1918     if(!findOpenBracket(doc, row, col, open))  {
1919         //KILE_DEBUG_MAIN << "no open bracket";
1920         return false;
1921     }
1922     if(!findCloseBracket(doc, row, col, close)) {
1923         //KILE_DEBUG_MAIN << "no close bracket";
1924         return false;
1925     }
1926 
1927     if(inside) {
1928         ++open.col;
1929     }
1930     else {
1931         ++close.col;
1932     }
1933 
1934     return true;
1935 }
1936 
1937 //////////////////// search for a bracket position  ////////////////////
1938 
1939 // Find the last opening bracket. If the current cursor is over
1940 //  - '{': we will stop immediately
1941 //  - '}': we will start before this character
1942 
findOpenBracket(KTextEditor::Document * doc,int row,int col,BracketData & bracket)1943 bool EditorExtension::findOpenBracket(KTextEditor::Document *doc, int row, int col, BracketData &bracket)
1944 {
1945     if(isBracketPosition(doc, row, col, bracket)) {
1946         // already found position?
1947         if(bracket.open) {
1948             return true;
1949         }
1950 
1951         // go one position back
1952         row = bracket.row;
1953         col = bracket.col;
1954         if(!decreaseCursorPosition(doc, row, col)) {
1955             return false;
1956         }
1957     }
1958 
1959     // looking back for last bracket
1960     return findOpenBracketTag(doc, row, col, bracket);
1961 }
1962 
1963 // Find the last closing bracket. If the current cursor is over
1964 //  - '}': we will stop immediately
1965 //  - '{': we will start behind this character
1966 
findCloseBracket(KTextEditor::Document * doc,int row,int col,BracketData & bracket)1967 bool EditorExtension::findCloseBracket(KTextEditor::Document *doc, int row, int col, BracketData &bracket)
1968 {
1969     if (isBracketPosition(doc, row, col, bracket)) {
1970         // already found position?
1971         if(!bracket.open) {
1972             return true;
1973         }
1974 
1975         // go one position forward
1976         row = bracket.row;
1977         col = bracket.col + 1;
1978     }
1979 
1980     // looking forward for next bracket
1981     return findCloseBracketTag(doc, row, col, bracket);
1982 }
1983 
1984 /*
1985    Bracket matching uses the following algorithm (taken from Kate):
1986    1) If in overwrite mode, match the bracket currently underneath the cursor.
1987    2) Otherwise, if the character to the left of the cursor is an ending bracket,
1988       match it.
1989    3) Otherwise if the character to the right of the cursor is a
1990       starting bracket, match it.
1991    4) Otherwise, if the character to the left of the cursor is a
1992       starting bracket, match it.
1993    5) Otherwise, if the character to the right of the cursor is an
1994       ending bracket, match it.
1995    6) Otherwise, don't match anything.
1996 */
1997 
isBracketPosition(KTextEditor::Document * doc,int row,int col,BracketData & bracket)1998 bool EditorExtension::isBracketPosition(KTextEditor::Document *doc, int row, int col, BracketData &bracket)
1999 {
2000     // default results
2001     bracket.row = row;
2002     bracket.col = col;
2003 
2004     QString textline = getTextLineReal(doc, row);
2005     QChar right = textline[col];
2006     QChar left  = (col > 0) ? textline[col-1] : QChar(' ');
2007 
2008     if (m_overwritemode) {
2009         if(right == '{') {
2010             bracket.open = true;
2011         }
2012         else if(left == '}') {
2013             bracket.open = false;
2014         }
2015         else {
2016             return false;
2017         }
2018     }
2019     else if(left == '}') {
2020         bracket.open = false;
2021         --bracket.col;
2022     }
2023     else if(right == '{') {
2024         bracket.open = true;
2025     }
2026     else if(left == '{') {
2027         bracket.open = true;
2028         --bracket.col;
2029     }
2030     else if(right == '}') {
2031         bracket.open = false;
2032     }
2033     else {
2034         return false;
2035     }
2036 
2037     return true;
2038 }
2039 
2040 // find next non-nested closing bracket
2041 
findCloseBracketTag(KTextEditor::Document * doc,int row,int col,BracketData & bracket)2042 bool EditorExtension::findCloseBracketTag(KTextEditor::Document *doc, int row, int col, BracketData &bracket)
2043 {
2044     uint brackets = 0;
2045     for(int line = row; line < doc->lines(); ++line) {
2046         uint start = (line == row) ? col : 0;
2047         QString textline = getTextLineReal(doc,line);
2048         for(int i = start; i < textline.length(); ++i) {
2049             if(textline[i] == '{') {
2050                 ++brackets;
2051             }
2052             else if(textline[i] == '}') {
2053                 if(brackets > 0) {
2054                     --brackets;
2055                 }
2056                 else {
2057                     bracket.row = line;
2058                     bracket.col = i;
2059                     bracket.open = false;
2060                     return true;
2061                 }
2062             }
2063         }
2064     }
2065 
2066     return false;
2067 }
2068 
2069 // find next non-nested opening bracket
2070 
findOpenBracketTag(KTextEditor::Document * doc,int row,int col,BracketData & bracket)2071 bool EditorExtension::findOpenBracketTag(KTextEditor::Document *doc, int row, int col, BracketData &bracket)
2072 {
2073     uint brackets = 0;
2074     for(int line = row; line >= 0; --line) {
2075         QString textline = getTextLineReal(doc, line);
2076         int start = (line == row) ? col : textline.length() - 1;
2077         for (int i = start; i >= 0; --i) {
2078             //KILE_DEBUG_MAIN << "findOpenBracketTag: (" << line << "," << i << ") = " << textline[i].toLatin1();
2079             if(textline[i] == '{') {
2080                 if(brackets > 0) {
2081                     --brackets;
2082                 }
2083                 else {
2084                     bracket.row = line;
2085                     bracket.col = i;
2086                     bracket.open = true;
2087                     return true;
2088                 }
2089             }
2090             else if(textline[i] == '}') {
2091                 ++brackets;
2092             }
2093         }
2094     }
2095 
2096     //KILE_DEBUG_MAIN << "nothting found";
2097     return false;
2098 }
2099 
2100 //////////////////// get real text ////////////////////
2101 
2102 // get current textline and remove
2103 //  - all pairs of backslashes: '\\'
2104 //  - all quoted comment signs: '\%'
2105 //  - all quoted brackets: '\{' and '\}'
2106 //  - all comments
2107 // replace these characters with one, which never will be looked for
2108 
getTextLineReal(KTextEditor::Document * doc,int row)2109 QString EditorExtension::getTextLineReal(KTextEditor::Document *doc, int row)
2110 {
2111     QString textline = doc->line(row);
2112     int len = textline.length();
2113     if(len == 0) {
2114         return QString();
2115     }
2116 
2117     bool backslash = false;
2118     for(int i = 0; i < len; ++i) {
2119         if (textline[i]=='{' || textline[i]=='}' || textline[i]=='$') {
2120             if(backslash) {
2121                 textline[i-1] = '&';
2122                 textline[i] = '&';
2123             }
2124             backslash = false;
2125         }
2126         else if(textline[i] == '\\') {
2127             if(backslash) {
2128                 textline[i-1] = '&';
2129                 textline[i] = '&';
2130                 backslash = false;
2131             }
2132             else {
2133                 backslash = true;
2134             }
2135         }
2136         else if(textline[i]=='%') {
2137             if (backslash) {
2138                 textline[i-1] = '&';
2139                 textline[i] = '&';
2140             }
2141             else {
2142                 len = i;
2143                 break;
2144             }
2145             backslash = false;
2146         }
2147         else {
2148             backslash = false;
2149         }
2150     }
2151 
2152     // return real text
2153     return textline.left(len);
2154 }
2155 
2156 //////////////////// capture the current word ////////////////////
2157 
2158 // Capture the current word from the cursor position to the left and right.
2159 // The result depens on the given search mode;
2160 // - smTex       only letters, except backslash as first and star as last  character
2161 // - smLetter:   only letters
2162 // - smWord:     letters and digits
2163 // - smNospace:  everything except white space
2164 
getCurrentWord(KTextEditor::Document * doc,int row,int col,EditorExtension::SelectMode mode,QString & word,int & x1,int & x2)2165 bool EditorExtension::getCurrentWord(KTextEditor::Document *doc, int row, int col, EditorExtension::SelectMode mode, QString &word, int &x1, int &x2)
2166 {
2167     // get real textline without comments, quoted characters and pairs of backslashes
2168     QString textline = getTextLineReal(doc, row);
2169     if (col > textline.length()) {
2170         return false;
2171     }
2172 
2173     QRegExp reg;
2174     QString pattern1, pattern2;
2175     switch(mode) {
2176     case smLetter:
2177         pattern1 = "[^a-zA-Z]+";
2178         pattern2 = "[a-zA-Z]+";
2179         break;
2180     case smWord:
2181         pattern1 = "[^a-zA-Z0-9]";
2182         pattern2 = "[a-zA-Z0-9]+";
2183         break;
2184     case smNospace:
2185         pattern1 = "\\s";
2186         pattern2 = "\\S+";
2187         break;
2188     default:
2189         pattern1 = "[^a-zA-Z]";
2190         pattern2 = "\\\\?[a-zA-Z]+\\*?";
2191         break;
2192     }
2193     x1 = x2 = col;
2194 
2195     int pos;
2196     // search to the left side
2197     if(col > 0) {
2198         reg.setPattern(pattern1);
2199         pos = textline.lastIndexOf(reg, col - 1);
2200         if(pos != -1) {        // found an illegal character
2201             x1 = pos + 1;
2202             if(mode == smTex) {
2203                 if(textline[pos] == '\\') {
2204                     x1 = pos;
2205                 }
2206                 col = x1;
2207             }
2208         }
2209         else {
2210             x1 = 0;               // pattern matches from beginning of line
2211         }
2212     }
2213 
2214     // search at the current position
2215     reg.setPattern(pattern2);
2216     pos = textline.indexOf(reg, col);
2217     if(pos != -1 && pos == col) {
2218         x2 = pos + reg.matchedLength();
2219     }
2220 
2221     // get all characters
2222     if(x1 != x2) {
2223         word = textline.mid(x1, x2 - x1);
2224         return true;
2225     }
2226     else {
2227         return false;
2228     }
2229 }
2230 
wordRange(const KTextEditor::Cursor & cursor,bool latexCommand,KTextEditor::View * view)2231 KTextEditor::Range EditorExtension::wordRange(const KTextEditor::Cursor &cursor, bool latexCommand, KTextEditor::View *view)
2232 {
2233     view = determineView(view);
2234     if(!view) {
2235         return KTextEditor::Range::invalid();
2236     }
2237 
2238     int col1, col2;
2239     QString word;
2240     EditorExtension::SelectMode mode = ( latexCommand ) ? EditorExtension::smTex : EditorExtension::smLetter;
2241     int line = cursor.line();
2242 
2243     return (getCurrentWord(view->document(), line, cursor.column(), mode, word, col1, col2))
2244            ? KTextEditor::Range(line,col1,line,col2)
2245            : KTextEditor::Range::invalid();
2246 }
2247 
word(const KTextEditor::Cursor & cursor,bool latexCommand,KTextEditor::View * view)2248 QString EditorExtension::word(const KTextEditor::Cursor &cursor, bool latexCommand, KTextEditor::View *view)
2249 {
2250     KTextEditor::Range range = EditorExtension::wordRange(cursor,latexCommand,view);
2251     return ( range.isValid() ) ? view->document()->text(range) : QString();
2252 }
2253 
2254 //////////////////// paragraph ////////////////////
2255 
selectParagraph(KTextEditor::View * view,bool wholeLines)2256 void EditorExtension::selectParagraph(KTextEditor::View* view, bool wholeLines)
2257 {
2258     view = determineView(view);
2259     if(!view) {
2260         return;
2261     }
2262 
2263     KTextEditor::Range range = findCurrentParagraphRange(view, wholeLines);
2264     if ( range.isValid() ) {
2265         view->setSelection(range);
2266     }
2267 }
2268 
deleteParagraph(KTextEditor::View * view)2269 void EditorExtension::deleteParagraph(KTextEditor::View *view)
2270 {
2271     view = determineView(view);
2272     if(!view) {
2273         return;
2274     }
2275     int startline, endline;
2276 
2277     if(findCurrentTexParagraph(startline, endline, view)) {
2278         KTextEditor::Document *doc = view->document();
2279         view->removeSelection();
2280         if(startline > 0) {
2281             --startline;
2282         }
2283         else if(endline < doc->lines() - 1) {
2284             ++endline;
2285         }
2286         doc->removeText(KTextEditor::Range(startline, 0, endline+1, 0));
2287         view->setCursorPosition(KTextEditor::Cursor(startline, 0));
2288     }
2289 }
2290 
2291 // get the range of the current paragraph
findCurrentParagraphRange(KTextEditor::View * view,bool wholeLines)2292 KTextEditor::Range EditorExtension::findCurrentParagraphRange(KTextEditor::View* view, bool wholeLines)
2293 {
2294     view = determineView(view);
2295     if(!view) {
2296         return KTextEditor::Range::invalid();
2297     }
2298 
2299     int startline, endline, startcolumn, endcolumn;
2300 
2301     if (findCurrentTexParagraph(startline, startcolumn, endline, endcolumn, view)) {
2302         return wholeLines ?
2303                KTextEditor::Range(startline, 0, endline + 1, 0) :
2304                KTextEditor::Range(startline, startcolumn, endline, endcolumn);
2305     }
2306     else {
2307         return KTextEditor::Range::invalid();
2308     }
2309 }
2310 
getParagraphText(KTextEditor::View * view)2311 QString  EditorExtension::getParagraphText(KTextEditor::View *view)
2312 {
2313     view = determineView(view);
2314     if(!view) {
2315         return QString();
2316     }
2317 
2318     KTextEditor::Range range = findCurrentParagraphRange(view);
2319     return (range.isValid()) ? view->document()->text(range) : QString();
2320 }
2321 
findCurrentTexParagraph(int & startline,int & endline,KTextEditor::View * view)2322 bool EditorExtension::findCurrentTexParagraph(int& startline, int& endline, KTextEditor::View* view)
2323 {
2324     int dummy;
2325     return findCurrentTexParagraph(startline, dummy, endline, dummy, view);
2326 }
2327 
findCurrentTexParagraph(int & startline,int & startcolumn,int & endline,int & endcolumn,KTextEditor::View * view)2328 bool EditorExtension::findCurrentTexParagraph(int& startline, int& startcolumn, int& endline, int& endcolumn, KTextEditor::View* view)
2329 {
2330     view = determineView(view);
2331     if(!view) {
2332         return false;
2333     }
2334 
2335     int row;
2336 
2337     // get current position
2338     KTextEditor::Document *doc = view->document();
2339     KTextEditor::Cursor cursor = view->cursorPosition();
2340     row = cursor.line();
2341 
2342     // don't accept an empty line as part of a paragraph
2343     if(doc->line(row).trimmed().isEmpty()) {
2344         return false;
2345     }
2346 
2347     // settings default results
2348     startline = row;
2349     endline = row;
2350 
2351     // find the previous empty line
2352     for(int line = row - 1; line >= 0; --line) {
2353         if(doc->line(line).trimmed().isEmpty()) {
2354             break;
2355         }
2356         startline = line;
2357     }
2358 
2359     // it is guaranteed that 'startline.trimmed()' won't be empty
2360     startcolumn = 0;
2361     QString line = doc->line(startline);
2362     for(int i = 0; i < line.size(); ++i) {
2363         if(!line[i].isSpace()) {
2364             startcolumn = i;
2365             break;
2366         }
2367     }
2368 
2369     // find the next empty line
2370     for(int line = row + 1; line < doc->lines(); ++line) {
2371         if(doc->line(line).trimmed().isEmpty()) {
2372             break;
2373         }
2374         endline = line;
2375     }
2376 
2377     // it is guaranteed that 'endline.trimmed()' won't be empty
2378     line = doc->line(endline);
2379     endcolumn = line.size();
2380     for(int i = line.size() - 1; i >= 0; --i) {
2381         if(!line[i].isSpace()) {
2382             endcolumn = i+1;
2383             break;
2384         }
2385     }
2386 
2387     // settings result
2388     return true;
2389 }
2390 
gotoNextParagraph(KTextEditor::View * view)2391 void EditorExtension::gotoNextParagraph(KTextEditor::View *view)
2392 {
2393     view = determineView(view);
2394     if(!view) {
2395         return;
2396     }
2397 
2398     bool found;
2399     int startline, endline;
2400     KTextEditor::Document *doc = view->document();
2401 
2402     endline = view->cursorPosition().line();
2403     if(doc->line(endline).trimmed().isEmpty()) {
2404         found = true;
2405     }
2406     else {
2407         found = findCurrentTexParagraph(startline, endline, view);
2408     }
2409 
2410     // we are in an empty line or in the last line of a paragraph
2411     if (found) {
2412         // find the next non empty line
2413         for(int line = endline + 1; line < doc->lines(); ++line) {
2414             if(!doc->line(line).trimmed().isEmpty()) {
2415                 view->setCursorPosition(KTextEditor::Cursor(line, 0));
2416                 return;
2417             }
2418         }
2419     }
2420 }
2421 
gotoPrevParagraph(KTextEditor::View * view)2422 void EditorExtension::gotoPrevParagraph(KTextEditor::View *view)
2423 {
2424     view = determineView(view);
2425     if(!view) {
2426         return;
2427     }
2428 
2429     bool found;
2430     int startline,endline;
2431     KTextEditor::Document *doc = view->document();
2432 
2433     startline = view->cursorPosition().line();
2434     if(doc->line(startline).trimmed().isEmpty()) {
2435         startline++;
2436         found = true;
2437     }
2438     else {
2439         found = findCurrentTexParagraph(startline,endline,view);
2440     }
2441     // we are in an empty line or in the first line of a paragraph
2442     if(found) {
2443         // find the last line of the previous paragraph
2444         int foundline = -1;
2445         for (int line = startline - 1; line >= 0; --line) {
2446             if(!doc->line(line).trimmed().isEmpty()) {
2447                 break;
2448             }
2449             foundline = line;
2450         }
2451         if(foundline < 0) {
2452             return;
2453         }
2454 
2455         // and finally the first line of this paragraph
2456         int prevstartline = -1;
2457         for(int line = foundline - 1; line >= 0; --line) {
2458             if(doc->line(line).trimmed().isEmpty()) {
2459                 break;
2460             }
2461             prevstartline = line;
2462         }
2463 
2464         if(prevstartline >= 0) {
2465             view->setCursorPosition(KTextEditor::Cursor(prevstartline, 0));
2466         }
2467     }
2468 }
2469 
prevNonEmptyLine(int line,KTextEditor::View * view)2470 int EditorExtension::prevNonEmptyLine(int line, KTextEditor::View *view)
2471 {
2472     view = determineView(view);
2473     if(!view) {
2474         return -1;
2475     }
2476 
2477     KTextEditor::Document *doc = view->document();
2478     for(int i = line - 1; i >= 0; --i) {
2479         if(!doc->line(i).trimmed().isEmpty()) {
2480             return i;
2481         }
2482     }
2483     return -1;
2484 }
2485 
nextNonEmptyLine(int line,KTextEditor::View * view)2486 int EditorExtension::nextNonEmptyLine(int line, KTextEditor::View *view)
2487 {
2488     view = determineView(view);
2489     if(!view) {
2490         return -1;
2491     }
2492 
2493     KTextEditor::Document *doc = view->document();
2494     int lines = doc->lines();
2495     for(int i = line + 1; i < lines; ++i) {
2496         if(!doc->line(i).trimmed().isEmpty()) {
2497             return i;
2498         }
2499     }
2500     return -1;
2501 }
2502 
2503 //////////////////// one line of text////////////////////
2504 
selectLine(KTextEditor::View * view)2505 void EditorExtension::selectLine(KTextEditor::View *view)
2506 {
2507     view = determineView(view);
2508     if(!view) {
2509         return;
2510     }
2511 
2512     // get current position
2513     int row;
2514     QString word;
2515     KTextEditor::Document *doc = view->document();
2516     KTextEditor::Cursor cursor = view->cursorPosition();
2517     row = cursor.line();
2518 
2519     if(doc->lineLength(row) > 0) {
2520         view->setSelection(KTextEditor::Range(row, 0, row + 1, 0));
2521     }
2522 }
2523 
selectLine(int line,KTextEditor::View * view)2524 void EditorExtension::selectLine(int line, KTextEditor::View *view)
2525 {
2526     view = determineView(view);
2527     if(!view) {
2528         return;
2529     }
2530 
2531     if(view->document()->lineLength(line) > 0) {
2532         view->setSelection(KTextEditor::Range(line, 0, line + 1, 0));
2533     }
2534 }
2535 
selectLines(int from,int to,KTextEditor::View * view)2536 void EditorExtension::selectLines(int from, int to, KTextEditor::View *view)
2537 {
2538     view = determineView(view);
2539     if(view && from <= to) {
2540         view->setSelection(KTextEditor::Range(from, 0, to + 1, 0));
2541     }
2542 }
2543 
replaceLine(int line,const QString & s,KTextEditor::View * view)2544 bool EditorExtension::replaceLine(int line, const QString &s, KTextEditor::View *view)
2545 {
2546     view = determineView(view);
2547     if(!view) {
2548         return false;
2549     }
2550 
2551     KTextEditor::Document *doc = view->document();
2552     KTextEditor::Document::EditingTransaction transaction(doc);
2553     doc->removeLine(line);
2554     bool result = doc->insertLine(line, s);
2555     return result;
2556 }
2557 
deleteEndOfLine(KTextEditor::View * view)2558 void EditorExtension::deleteEndOfLine(KTextEditor::View *view)
2559 {
2560     view = determineView(view);
2561     if(!view) {
2562         return;
2563     }
2564 
2565     int row, col;
2566     KTextEditor::Cursor cursor = view->cursorPosition();
2567     row = cursor.line();
2568     col = cursor.column();
2569 
2570     KTextEditor::Document *doc = view->document();
2571     view->removeSelection();
2572     doc->removeText(KTextEditor::Range(row, col, row, doc->lineLength(row)));
2573 }
2574 
2575 //////////////////// LaTeX command ////////////////////
2576 
selectWord(EditorExtension::SelectMode mode,KTextEditor::View * view)2577 void EditorExtension::selectWord(EditorExtension::SelectMode mode, KTextEditor::View *view)
2578 {
2579     view = determineView(view);
2580     if(!view) {
2581         return;
2582     }
2583 
2584     KTextEditor::Range range = wordRange(view->cursorPosition(),mode,view);
2585     if ( range.isValid() ) {
2586         view->setSelection(range);
2587     }
2588 }
2589 
deleteWord(EditorExtension::SelectMode mode,KTextEditor::View * view)2590 void EditorExtension::deleteWord(EditorExtension::SelectMode mode, KTextEditor::View *view)
2591 {
2592     view = determineView(view);
2593     if(!view) {
2594         return;
2595     }
2596 
2597     KTextEditor::Range range = wordRange(view->cursorPosition(),mode,view);
2598     if(range.isValid()) {
2599         deleteRange(range,view);
2600     }
2601 }
2602 
nextBullet(KTextEditor::View * view)2603 void EditorExtension::nextBullet(KTextEditor::View* view)
2604 {
2605     gotoBullet(false, view);
2606 }
2607 
prevBullet(KTextEditor::View * view)2608 void EditorExtension::prevBullet(KTextEditor::View* view)
2609 {
2610     gotoBullet(true, view);
2611 }
2612 
insertBullet(KTextEditor::View * view)2613 void EditorExtension::insertBullet(KTextEditor::View* view)
2614 {
2615     view = determineView(view);
2616     if(!view) {
2617         return;
2618     }
2619 
2620     view->document()->insertText(view->cursorPosition(), s_bullet);
2621 }
2622 
2623 ///////////////////// Special Functions ///////////////
2624 /*
2625 void EditorExtension::insertNewLine(KTextEditor::View *view)
2626 {
2627 	view = determineView(view);
2628 	if(!view) {
2629 		return;
2630 	}
2631 
2632 	int newLineNumber = view->cursorPosition().line() + 1;
2633 	view->document()->insertLine(newLineNumber, QString());
2634 }
2635 */
moveCursorToLastPositionInCurrentLine(KTextEditor::View * view)2636 void EditorExtension::moveCursorToLastPositionInCurrentLine(KTextEditor::View *view)
2637 {
2638     view = determineView(view);
2639     if(!view) {
2640         return;
2641     }
2642 
2643     const KTextEditor::Cursor currentPosition = view->cursorPosition();
2644     view->setCursorPosition(KTextEditor::Cursor(currentPosition.line(),
2645                             view->document()->lineLength(currentPosition.line())));
2646 }
2647 
keyReturn(KTextEditor::View * view)2648 void EditorExtension::keyReturn(KTextEditor::View *view)
2649 {
2650     view = determineView(view);
2651     if(!view) {
2652         return;
2653     }
2654 
2655     int newLineNumber = view->cursorPosition().line() + 1;
2656     view->document()->insertLine(newLineNumber, QString());
2657     view->setCursorPosition(KTextEditor::Cursor(newLineNumber, 0));
2658 }
2659 
commentLaTeX(KTextEditor::Document * document,const KTextEditor::Range & range)2660 void EditorExtension::commentLaTeX(KTextEditor::Document* document, const KTextEditor::Range& range)
2661 {
2662     int startLine = range.start().line(), endLine = range.end().line();
2663     for(int i = startLine; i <= endLine; ++i) {
2664         document->insertText(KTextEditor::Cursor(i, 0), "% ");
2665     }
2666 }
2667 
goToLine(int line,KTextEditor::View * view)2668 void EditorExtension::goToLine(int line, KTextEditor::View *view)
2669 {
2670     view = determineView(view);
2671     if(!view) {
2672         return;
2673     }
2674 
2675     KTextEditor::Cursor cursor(line, 0);
2676     view->setCursorPosition(cursor);
2677 }
2678 
2679 //////////////////// double quotes ////////////////////
2680 
initDoubleQuotes()2681 void EditorExtension::initDoubleQuotes()
2682 {
2683     m_dblQuotes = KileConfig::insertDoubleQuotes();
2684 
2685     int index = KileConfig::doubleQuotes();
2686     if(index < 0 || index >= m_quoteList.count()) {
2687         index = 0;
2688     }
2689 
2690     m_leftDblQuote = m_quoteList[index].first;
2691     m_rightDblQuote = m_quoteList[index].second;
2692     KILE_DEBUG_MAIN << "new quotes: " << m_dblQuotes << " left=" << m_leftDblQuote << " right=" << m_rightDblQuote<< endl;
2693 }
2694 
insertDoubleQuotes(KTextEditor::View * view)2695 bool EditorExtension::insertDoubleQuotes(KTextEditor::View *view)
2696 {
2697     // don't insert double quotes, if konsole has focus
2698     // return false, because if this is called from an event
2699     // handler, because this event has to be passed on
2700     if(m_ki->texKonsole()->hasFocus()) {
2701         return false;
2702     }
2703 
2704     // insert double quotes, normal mode or autocompletion mode
2705     // always return true for event handler
2706     view = determineView(view);
2707     if(!view) {
2708         return true;
2709     }
2710 
2711     KTextEditor::Document *doc = view->document();
2712 
2713     if(!doc) {
2714         return false;
2715     }
2716 
2717     view->removeSelectionText();
2718 
2719     int row, col;
2720     KTextEditor::Cursor cursor = view->cursorPosition();
2721     row = cursor.line();
2722     col = cursor.column();
2723 
2724     // simply insert, if we are inside a verb command
2725     if(insideVerb(view) || insideVerbatim(view)) {
2726         return false;
2727     }
2728 
2729     // simply insert, if autoinsert mode is not active or the char bevor is \ (typically for \"a useful)
2730     if (!m_dblQuotes || (col > 0 && doc->text(KTextEditor::Range(row, col - 1, row, col)) == "\\")) {
2731         return false;
2732     }
2733 
2734     // insert with auto mode
2735     QString pattern1 = QRegExp::escape(m_leftDblQuote);
2736     if(m_leftDblQuote.at(m_leftDblQuote.length()-1).isLetter()) {
2737         pattern1 += "(\\b|(\\{\\}))";
2738     }
2739     QString pattern2 = QRegExp::escape(m_rightDblQuote);
2740     if(m_rightDblQuote.at(m_rightDblQuote.length()-1).isLetter()) {
2741         pattern2 += "(\\b|(\\{\\}))";
2742     }
2743 
2744     bool openFound = false;
2745     KTextEditor::Range searchRange = KTextEditor::Range(KTextEditor::Cursor(0, 0), KTextEditor::Cursor(row, col));
2746     QVector<KTextEditor::Range> foundRanges = doc->searchText(searchRange, '(' + pattern1 + ")|(" + pattern2 + ')',
2747             KTextEditor::Regex | KTextEditor::Backwards);
2748     // KTextEditor::Document#searchText always returns at least one range, even
2749     // if no occurrences have been found. Thus, we have to check if the range is valid.
2750     KTextEditor::Range range = foundRanges.first();
2751     if(range.isValid()) {
2752         int lineFound = range.start().line();
2753         int columnFound = range.start().column();
2754         openFound = (doc->line(lineFound).indexOf(m_leftDblQuote, columnFound) == columnFound);
2755     }
2756 
2757     QString textline = doc->line(row);
2758     //KILE_DEBUG_MAIN << "text=" << textline << " open=" << openfound;
2759     if(openFound) {
2760         // If we last inserted a language specific doublequote open,
2761         // we have to change it to a normal doublequote. If not we
2762         // insert a language specific doublequote close
2763         int startcol = col - m_leftDblQuote.length();
2764         //KILE_DEBUG_MAIN << "startcol=" << startcol << " col=" << col ;
2765         if (startcol>=0 && textline.indexOf(m_leftDblQuote, startcol) == startcol) {
2766             doc->removeText(KTextEditor::Range(row, startcol, row, startcol + m_leftDblQuote.length()));
2767             doc->insertText(KTextEditor::Cursor(row, startcol), "\"");
2768         }
2769         else {
2770             doc->insertText(KTextEditor::Cursor(row, col), m_rightDblQuote);
2771         }
2772     }
2773     else {
2774         // If we last inserted a language specific doublequote close,
2775         // we have to change it to a normal doublequote. If not we
2776         // insert a language specific doublequote open
2777         int startcol = col - m_rightDblQuote.length();
2778         //KILE_DEBUG_MAIN << "startcol=" << startcol << " col=" << col ;
2779         if (startcol >= 0 && textline.indexOf(m_rightDblQuote, startcol) == startcol) {
2780             doc->removeText(KTextEditor::Range(row, startcol, row, startcol + m_rightDblQuote.length()));
2781             doc->insertText(KTextEditor::Cursor(row,startcol), "\"");
2782         }
2783         else {
2784             doc->insertText(KTextEditor::Cursor(row, col), m_leftDblQuote);
2785         }
2786     }
2787     return true;
2788 }
2789 
2790 // Takes an unicode unsigned short and calls insertSpecialCharacter to
2791 // insert the proper LaTeX sequence and warn user of any dependencies.
2792 //FIXME: there should be one central place to convert unicode chars to LaTeX;
2793 //       also see 'LaTeXEventFilter::eventFilter'.
insertLatexFromUnicode(unsigned short rep,KTextEditor::View * view)2794 bool EditorExtension::insertLatexFromUnicode(unsigned short rep, KTextEditor::View *view)
2795 {
2796     switch(rep)
2797     {   // Find the unicode decimal representation
2798     case 160:
2799         return insertSpecialCharacter("~", view);
2800     case 161:
2801         return insertSpecialCharacter("!`", view);
2802     case 162:
2803         return insertSpecialCharacter("\\textcent", view, "textcomp");
2804     case 163:
2805         return insertSpecialCharacter("\\pounds", view);
2806     case 164:
2807         return insertSpecialCharacter("\\textcurrency", view, "textcomp");
2808     case 165:
2809         return insertSpecialCharacter("\\textyen", view, "textcomp");
2810     case 166:
2811         return insertSpecialCharacter("\\textbrokenbar", view, "textcomp");
2812     case 167:
2813         return insertSpecialCharacter("\\S", view);
2814     case 168:
2815         return insertSpecialCharacter("\"", view);
2816     case 169:
2817         return insertSpecialCharacter("\\copyright", view);
2818     case 170:
2819         return insertSpecialCharacter("\\textordfeminine", view, "textcomp");
2820     case 171:
2821         return insertSpecialCharacter("\\guillemotleft", view);
2822     case 172:
2823         return insertSpecialCharacter("\\neg", view); // TODO: Check for math
2824     case 173:
2825         return insertSpecialCharacter("-", view); // TODO: Check for math
2826     case 174:
2827         return insertSpecialCharacter("\\textregistered", view, "textcomp");
2828     case 176:
2829         return insertSpecialCharacter("^\\circ", view); // TODO: Check for math
2830     case 177:
2831         return insertSpecialCharacter("\\pm", view); // TODO: Check for math
2832     case 178:
2833         return insertSpecialCharacter("^2", view); // TODO: Check for math
2834     case 179:
2835         return insertSpecialCharacter("^3", view); // TODO: Check for math
2836     case 180:
2837         return insertSpecialCharacter("'", view);
2838     case 181:
2839         return insertSpecialCharacter("\\mu", view); // TODO: Check for math
2840     case 182:
2841         return insertSpecialCharacter("\\P", view);
2842     case 185:
2843         return insertSpecialCharacter("^1", view); // TODO: Check for math
2844     case 186:
2845         return insertSpecialCharacter("\\textordmasculine", view, "textcomp");
2846     case 187:
2847         return insertSpecialCharacter("\\guillemotright", view);
2848     case 191:
2849         return insertSpecialCharacter("?`", view);
2850     case 192:
2851         return insertSpecialCharacter("\\`A", view);
2852     case 193:
2853         return insertSpecialCharacter("\\'A", view);
2854     case 194:
2855         return insertSpecialCharacter("\\^A", view);
2856     case 195:
2857         return insertSpecialCharacter("\\~A", view);
2858     case 196:
2859         return insertSpecialCharacter("\\\"A", view);
2860     case 197:
2861         return insertSpecialCharacter("\\AA", view);
2862     case 198:
2863         return insertSpecialCharacter("\\AE", view);
2864     case 199:
2865         return insertSpecialCharacter("\\c{C}", view);
2866     case 200:
2867         return insertSpecialCharacter("\\`E", view);
2868     case 201:
2869         return insertSpecialCharacter("\\'E", view);
2870     case 202:
2871         return insertSpecialCharacter("\\^E", view);
2872     case 203:
2873         return insertSpecialCharacter("\\\"E", view);
2874     case 204:
2875         return insertSpecialCharacter("\\`I", view);
2876     case 205:
2877         return insertSpecialCharacter("\\'I", view);
2878     case 206:
2879         return insertSpecialCharacter("\\^I", view);
2880     case 207:
2881         return insertSpecialCharacter("\\\"I", view);
2882     case 209:
2883         return insertSpecialCharacter("\\~N", view);
2884     case 210:
2885         return insertSpecialCharacter("\\`O", view);
2886     case 211:
2887         return insertSpecialCharacter("\\'O", view);
2888     case 212:
2889         return insertSpecialCharacter("\\^O", view);
2890     case 213:
2891         return insertSpecialCharacter("\\~O", view);
2892     case 214:
2893         return insertSpecialCharacter("\\\"O", view);
2894     case 215:
2895         return insertSpecialCharacter("\\times", view); //TODO: Check for math
2896     case 216:
2897         return insertSpecialCharacter("\\Oslash", view);
2898     case 217:
2899         return insertSpecialCharacter("\\`U", view);
2900     case 218:
2901         return insertSpecialCharacter("\\'U", view);
2902     case 219:
2903         return insertSpecialCharacter("\\^U", view);
2904     case 220:
2905         return insertSpecialCharacter("\\\"U", view);
2906     case 221:
2907         return insertSpecialCharacter("\\'Y", view);
2908     case 223:
2909         return insertSpecialCharacter("\\ss{}", view);
2910     case 224:
2911         return insertSpecialCharacter("\\`a", view);
2912     case 225:
2913         return insertSpecialCharacter("\\'a", view);
2914     case 226:
2915         return insertSpecialCharacter("\\^a", view);
2916     case 227:
2917         return insertSpecialCharacter("\\~a", view);
2918     case 228:
2919         return insertSpecialCharacter("\\\"a", view);
2920     case 229:
2921         return insertSpecialCharacter("\\aa", view);
2922     case 230:
2923         return insertSpecialCharacter("\\ae", view);
2924     case 231:
2925         return insertSpecialCharacter("\\c{c}", view);
2926     case 232:
2927         return insertSpecialCharacter("\\`e", view);
2928     case 233:
2929         return insertSpecialCharacter("\\'e", view);
2930     case 234:
2931         return insertSpecialCharacter("\\^e", view);
2932     case 235:
2933         return insertSpecialCharacter("\\\"e", view);
2934     case 236:
2935         return insertSpecialCharacter("\\`i", view);
2936     case 237:
2937         return insertSpecialCharacter("\\'i", view);
2938     case 238:
2939         return insertSpecialCharacter("\\^i", view);
2940     case 239:
2941         return insertSpecialCharacter("\\\"i", view);
2942     case 240:
2943         return insertSpecialCharacter("\\~o", view);
2944     case 241:
2945         return insertSpecialCharacter("\\~n", view);
2946     case 242:
2947         return insertSpecialCharacter("\\`o", view);
2948     case 243:
2949         return insertSpecialCharacter("\\'o", view);
2950     case 244:
2951         return insertSpecialCharacter("\\^o", view);
2952     case 245:
2953         return insertSpecialCharacter("\\~o", view);
2954     case 246:
2955         return insertSpecialCharacter("\\\"o", view);
2956     case 247:
2957         return insertSpecialCharacter("\\div", view);
2958     case 248:
2959         return insertSpecialCharacter("\\oslash", view);
2960     case 249:
2961         return insertSpecialCharacter("\\`u", view);
2962     case 250:
2963         return insertSpecialCharacter("\\'u", view);
2964     case 251:
2965         return insertSpecialCharacter("\\^u", view);
2966     case 252:
2967         return insertSpecialCharacter("\\\"u", view);
2968     case 253:
2969         return insertSpecialCharacter("\\'y", view);
2970     case 255:
2971         return insertSpecialCharacter("\\\"y", view);
2972     case 256:
2973         return insertSpecialCharacter("\\=A", view);
2974     case 257:
2975         return insertSpecialCharacter("\\=a", view);
2976     case 258:
2977         return insertSpecialCharacter("\\uA", view);
2978     case 259:
2979         return insertSpecialCharacter("\\ua", view);
2980     case 262:
2981         return insertSpecialCharacter("\\'C", view);
2982     case 263:
2983         return insertSpecialCharacter("\\'c", view);
2984     case 264:
2985         return insertSpecialCharacter("\\^C", view);
2986     case 265:
2987         return insertSpecialCharacter("\\^c", view);
2988     case 266:
2989         return insertSpecialCharacter("\\.C", view);
2990     case 267:
2991         return insertSpecialCharacter("\\.c", view);
2992     case 268:
2993         return insertSpecialCharacter("\\vC", view);
2994     case 269:
2995         return insertSpecialCharacter("\\vc", view);
2996     case 270:
2997         return insertSpecialCharacter("\\vD", view);
2998     case 271:
2999         return insertSpecialCharacter("\\vd", view);
3000     case 272:
3001         return insertSpecialCharacter("\\=D", view);
3002     case 273:
3003         return insertSpecialCharacter("\\=d", view);
3004     case 274:
3005         return insertSpecialCharacter("\\=E", view);
3006     case 275:
3007         return insertSpecialCharacter("\\=e", view);
3008     case 276:
3009         return insertSpecialCharacter("\\uE", view);
3010     case 277:
3011         return insertSpecialCharacter("\\ue", view);
3012     case 278:
3013         return insertSpecialCharacter("\\.E", view);
3014     case 279:
3015         return insertSpecialCharacter("\\.e", view);
3016     case 282:
3017         return insertSpecialCharacter("\\vE", view);
3018     case 283:
3019         return insertSpecialCharacter("\\ve", view);
3020     case 284:
3021         return insertSpecialCharacter("\\^G", view);
3022     case 285:
3023         return insertSpecialCharacter("\\^g", view);
3024     case 286:
3025         return insertSpecialCharacter("\\uG", view);
3026     case 287:
3027         return insertSpecialCharacter("\\ug", view);
3028     case 288:
3029         return insertSpecialCharacter("\\.G", view);
3030     case 289:
3031         return insertSpecialCharacter("\\.g", view);
3032     case 290:
3033         return insertSpecialCharacter("\\cG", view);
3034     case 291:
3035         return insertSpecialCharacter("\\'g", view);
3036     case 292:
3037         return insertSpecialCharacter("\\^H", view);
3038     case 293:
3039         return insertSpecialCharacter("\\^h", view);
3040     case 294:
3041         return insertSpecialCharacter("\\=H", view);
3042     case 295:
3043         return insertSpecialCharacter("\\=h", view);
3044     case 296:
3045         return insertSpecialCharacter("\\~I", view);
3046     case 297:
3047         return insertSpecialCharacter("\\~i", view);
3048     case 298:
3049         return insertSpecialCharacter("\\=I", view);
3050     case 299:
3051         return insertSpecialCharacter("\\=i", view);
3052     case 300:
3053         return insertSpecialCharacter("\\uI", view);
3054     case 301:
3055         return insertSpecialCharacter("\\ui", view);
3056     case 304:
3057         return insertSpecialCharacter("\\.I", view);
3058     case 305:
3059         return insertSpecialCharacter("\\i", view);
3060     case 308:
3061         return insertSpecialCharacter("\\^J", view);
3062     case 309:
3063         return insertSpecialCharacter("\\^j", view);
3064     case 310:
3065         return insertSpecialCharacter("\\cK", view);
3066     case 311:
3067         return insertSpecialCharacter("\\ck", view);
3068     case 313:
3069         return insertSpecialCharacter("\\'L", view);
3070     case 314:
3071         return insertSpecialCharacter("\\'l", view);
3072     case 315:
3073         return insertSpecialCharacter("\\cL", view);
3074     case 316:
3075         return insertSpecialCharacter("\\cl", view);
3076     case 317:
3077         return insertSpecialCharacter("\\vL", view);
3078     case 318:
3079         return insertSpecialCharacter("\\vl", view);
3080     case 323:
3081         return insertSpecialCharacter("\\'N", view);
3082     case 324:
3083         return insertSpecialCharacter("\\'n", view);
3084     case 325:
3085         return insertSpecialCharacter("\\cN", view);
3086     case 326:
3087         return insertSpecialCharacter("\\cn", view);
3088     case 327:
3089         return insertSpecialCharacter("\\vN", view);
3090     case 328:
3091         return insertSpecialCharacter("\\vn", view);
3092     case 332:
3093         return insertSpecialCharacter("\\=O", view);
3094     case 333:
3095         return insertSpecialCharacter("\\=o", view);
3096     case 334:
3097         return insertSpecialCharacter("\\uO", view);
3098     case 335:
3099         return insertSpecialCharacter("\\uo", view);
3100     case 336:
3101         return insertSpecialCharacter("\\HO", view);
3102     case 337:
3103         return insertSpecialCharacter("\\Ho", view);
3104     case 338:
3105         return insertSpecialCharacter("\\OE", view);
3106     case 339:
3107         return insertSpecialCharacter("\\oe", view);
3108     case 340:
3109         return insertSpecialCharacter("\\'R", view);
3110     case 341:
3111         return insertSpecialCharacter("\\'r", view);
3112     case 342:
3113         return insertSpecialCharacter("\\cR", view);
3114     case 343:
3115         return insertSpecialCharacter("\\cr", view);
3116     case 344:
3117         return insertSpecialCharacter("\\vR", view);
3118     case 345:
3119         return insertSpecialCharacter("\\vr", view);
3120     case 346:
3121         return insertSpecialCharacter("\\'S", view);
3122     case 347:
3123         return insertSpecialCharacter("\\'s", view);
3124     case 348:
3125         return insertSpecialCharacter("\\^S", view);
3126     case 349:
3127         return insertSpecialCharacter("\\^s", view);
3128     case 350:
3129         return insertSpecialCharacter("\\cS", view);
3130     case 351:
3131         return insertSpecialCharacter("\\cs", view);
3132     case 352:
3133         return insertSpecialCharacter("\\vS", view);
3134     case 353:
3135         return insertSpecialCharacter("\\vs", view);
3136     case 354:
3137         return insertSpecialCharacter("\\cT", view);
3138     case 355:
3139         return insertSpecialCharacter("\\ct", view);
3140     case 356:
3141         return insertSpecialCharacter("\\vT", view);
3142     case 357:
3143         return insertSpecialCharacter("\\vt", view);
3144     case 358:
3145         return insertSpecialCharacter("\\=T", view);
3146     case 359:
3147         return insertSpecialCharacter("\\=t", view);
3148     case 360:
3149         return insertSpecialCharacter("\\~U", view);
3150     case 361:
3151         return insertSpecialCharacter("\\~u", view);
3152     case 362:
3153         return insertSpecialCharacter("\\=U", view);
3154     case 363:
3155         return insertSpecialCharacter("\\=u", view);
3156     case 364:
3157         return insertSpecialCharacter("\\uU", view);
3158     case 365:
3159         return insertSpecialCharacter("\\uu", view);
3160     case 366:
3161         return insertSpecialCharacter("\\AU", view);
3162     case 367:
3163         return insertSpecialCharacter("\\Au", view);
3164     case 368:
3165         return insertSpecialCharacter("\\HU", view);
3166     case 369:
3167         return insertSpecialCharacter("\\Hu", view);
3168     case 370:
3169         return insertSpecialCharacter("\\cU", view);
3170     case 371:
3171         return insertSpecialCharacter("\\cu", view);
3172     case 372:
3173         return insertSpecialCharacter("\\^W", view);
3174     case 373:
3175         return insertSpecialCharacter("\\^w", view);
3176     case 374:
3177         return insertSpecialCharacter("\\^Y", view);
3178     case 375:
3179         return insertSpecialCharacter("\\^y", view);
3180     case 376:
3181         return insertSpecialCharacter("\\\"Y", view);
3182     case 377:
3183         return insertSpecialCharacter("\\'Z", view);
3184     case 378:
3185         return insertSpecialCharacter("\\'z", view);
3186     case 379:
3187         return insertSpecialCharacter("\\.Z", view);
3188     case 380:
3189         return insertSpecialCharacter("\\.z", view);
3190     case 381:
3191         return insertSpecialCharacter("\\vZ", view);
3192     case 382:
3193         return insertSpecialCharacter("\\vz", view);
3194     default:
3195         return false;
3196     }
3197 }
3198 
3199 // If allowed, inserts texString at current cursor postition. Startlingly similar to insertDoubleQuotes.
insertSpecialCharacter(const QString & texString,KTextEditor::View * view,const QString & dep)3200 bool EditorExtension::insertSpecialCharacter(const QString& texString, KTextEditor::View *view, const QString& dep)
3201 {
3202     // stop if special character replacement is disabled
3203     if (!m_specialCharacters) {
3204         return false;
3205     }
3206 
3207     // return false if konsole has focus
3208     if(m_ki->texKonsole()->hasFocus()) {
3209         return false;
3210     }
3211 
3212     // always return true for event handler
3213     view = determineView(view);
3214     if(!view) {
3215         return true;
3216     }
3217 
3218     KTextEditor::Document *doc = view->document();
3219 
3220     // Only change if we have a tex document
3221     if(!doc || !m_ki->extensions()->isTexFile(doc->url())) {
3222         return false;
3223     }
3224 
3225     // In case of replace
3226     view->removeSelectionText();
3227 
3228     int row, col;
3229     KTextEditor::Cursor cursor = view->cursorPosition();
3230     row = cursor.line();
3231     col = cursor.column();
3232 
3233     // insert texString
3234     doc->insertText(KTextEditor::Cursor(row, col), texString);
3235 
3236     KILE_DEBUG_MAIN << "Replacing with "<<texString;
3237 
3238     // Check dependency
3239     if (!dep.isEmpty()) {
3240         QStringList packagelist = m_ki->allPackages();
3241         if(!packagelist.contains(dep)) {
3242             m_ki->errorHandler()->printMessage(KileTool::Error, i18n("You have to include the package %1 to use %2.", dep, texString), i18n("Missing Package"));
3243             KILE_DEBUG_MAIN << "Need package "<< dep;
3244         }
3245     }
3246 
3247     return true;
3248 }
3249 
3250 //////////////////// insert tabulator ////////////////////
3251 
insertIntelligentTabulator(KTextEditor::View * view)3252 void EditorExtension::insertIntelligentTabulator(KTextEditor::View *view)
3253 {
3254     view = determineView(view);
3255     if(!view) {
3256         return;
3257     }
3258 
3259     int row, col, currentRow, currentCol;
3260     QString envname,tab;
3261     QString prefix = " ";
3262 
3263     KTextEditor::Cursor cursor = view->cursorPosition();
3264     currentRow = cursor.line();
3265     currentCol = cursor.column();
3266     if(findOpenedEnvironment(row, col, envname, view)) {
3267         // look if this is an environment with tabs
3268         tab = m_latexCommands->getTabulator(envname);
3269 
3270         // try to align tabulator with textline above
3271         if(currentRow >= 1) {
3272             int tabpos = view->document()->line(currentRow - 1).indexOf('&', currentCol);
3273             if(tabpos >= 0) {
3274                 currentCol = tabpos;
3275                 prefix.clear();
3276             }
3277         }
3278     }
3279 
3280     if(tab.isEmpty()) {
3281         tab = '&';
3282     }
3283     tab = prefix + tab + ' ';
3284 
3285     view->document()->insertText(KTextEditor::Cursor(currentRow, currentCol), tab);
3286     view->setCursorPosition(KTextEditor::Cursor(currentRow, currentCol + tab.length()));
3287 }
3288 
3289 //////////////////// autocomplete environment ////////////////////
3290 
3291 // should we complete the current environment (call from LaTeXEventFilter)
3292 
eventInsertEnvironment(KTextEditor::View * view)3293 bool EditorExtension::eventInsertEnvironment(KTextEditor::View *view)
3294 {
3295     if(!view) {
3296         return false;
3297     }
3298 
3299     // don't complete environment, if we are
3300     // still working inside the completion box
3301     KTextEditor::CodeCompletionInterface *codeCompletionInterface
3302         = qobject_cast<KTextEditor::CodeCompletionInterface*>(view);
3303     if(codeCompletionInterface && codeCompletionInterface->isCompletionActive()) {
3304         return false;
3305     }
3306 
3307     int row = view->cursorPosition().line();
3308     int col = view->cursorPositionVirtual().column();
3309     QString line = view->document()->line(row).left(col);
3310 
3311     int pos = m_regexpEnter.indexIn(line);
3312     if (pos != -1) {
3313         line = m_regexpEnter.cap(1);
3314         for(int i = 0; i < line.length(); ++i) {
3315             if(!line[i].isSpace()) {
3316                 line[i] = ' ';
3317             }
3318         }
3319 
3320         QString envname, endenv;
3321         if(m_regexpEnter.cap(2) == "\\[") {
3322             envname = m_regexpEnter.cap(2);
3323             endenv = "\\]\n";
3324         }
3325         else {
3326             envname = m_regexpEnter.cap(4);
3327             endenv = m_regexpEnter.cap(2).replace("\\begin","\\end") + '\n';
3328         }
3329 
3330         if(shouldCompleteEnv(envname, view)) {
3331             QString item =  m_latexCommands->isListEnv(envname) ? "\\item " : QString();
3332             view->document()->insertText(KTextEditor::Cursor(row, col), '\n' + line + m_envAutoIndent + item + '\n' + line + endenv);
3333             view->setCursorPosition(KTextEditor::Cursor(row + 1, line.length() + m_envAutoIndent.length() + item.length()));
3334             return true;
3335         }
3336     }
3337     return false;
3338 }
3339 
shouldCompleteEnv(const QString & env,KTextEditor::View * view)3340 bool EditorExtension::shouldCompleteEnv(const QString &env, KTextEditor::View *view)
3341 {
3342     KILE_DEBUG_MAIN << "===EditorExtension::shouldCompleteEnv( " << env << " )===";
3343     QRegExp reTestBegin,reTestEnd;
3344     if(env == "\\[") {
3345         KILE_DEBUG_MAIN << "display style";
3346         reTestBegin.setPattern("(?:[^\\\\]|^)\\\\\\[");
3347         // the first part is a non-capturing bracket (?:...) and we check if we don't have a backslash in front,
3348         //  or that we are at the begin of the line
3349         reTestEnd.setPattern("(?:[^\\\\]|^)\\\\\\]");
3350     }
3351     else {
3352         reTestBegin.setPattern("(?:[^\\\\]|^)\\\\begin\\s*\\{" + QRegExp::escape(env) + "\\}");
3353         reTestEnd.setPattern("(?:[^\\\\]|^)\\\\end\\s*\\{" + QRegExp::escape(env) + "\\}");
3354     }
3355 
3356     int num = view->document()->lines();
3357     int numBeginsFound = 0;
3358     int numEndsFound = 0;
3359     KTextEditor::Cursor cursor = view->cursorPosition();
3360     int realLine = cursor.line();
3361 
3362     for(int i = realLine; i < num; ++i) {
3363         numBeginsFound += view->document()->line(i).count(reTestBegin);
3364         numEndsFound += view->document()->line(i).count(reTestEnd);
3365         KILE_DEBUG_MAIN << "line is " << i <<  " numBeginsFound = " << numBeginsFound <<  " , " << "numEndsFound = " << numEndsFound;
3366         if(numEndsFound >= numBeginsFound) {
3367             return false;
3368         }
3369         else if(numEndsFound == 0 && numBeginsFound > 1) {
3370             return true;
3371         }
3372         else if(numBeginsFound > 2 || numEndsFound > 1) {
3373             return true; // terminate the search
3374         }
3375     }
3376 
3377     return true;
3378 }
3379 
getWhiteSpace(const QString & s)3380 QString EditorExtension::getWhiteSpace(const QString &s)
3381 {
3382     QString whitespace = s;
3383 
3384     for(int i = 0; i < whitespace.length(); ++i) {
3385         if(!whitespace[i].isSpace()) {
3386             whitespace[i] = ' ';
3387         }
3388     }
3389     return whitespace;
3390 }
3391 
3392 //////////////////// inside verbatim commands ////////////////////
3393 
insideVerbatim(KTextEditor::View * view)3394 bool EditorExtension::insideVerbatim(KTextEditor::View *view)
3395 {
3396     int rowEnv, colEnv;
3397     QString nameEnv;
3398 
3399     if(findOpenedEnvironment(rowEnv, colEnv, nameEnv, view)) {
3400         if(m_latexCommands->isVerbatimEnv(nameEnv)) {
3401             return true;
3402         }
3403     }
3404 
3405     return false;
3406 }
3407 
insideVerb(KTextEditor::View * view)3408 bool EditorExtension::insideVerb(KTextEditor::View *view)
3409 {
3410     view = determineView(view);
3411     if(!view) {
3412         return false;
3413     }
3414 
3415     // get current position
3416     int row, col;
3417     KTextEditor::Cursor cursor = view->cursorPosition();
3418     row = cursor.line();
3419     col = cursor.column();
3420 
3421     int startpos = 0;
3422     QString textline = getTextLineReal(view->document(),row);
3423     QRegExp reg("\\\\verb(\\*?)(.)");
3424     while(true) {
3425         int pos = textline.indexOf(reg,startpos);
3426         if(pos < 0 || col < pos + 6 + reg.cap(1).length()) {
3427             return false;
3428         }
3429         pos = textline.indexOf(reg.cap(2), pos + 6 + reg.cap(1).length());
3430         if(pos < 0 || col <= pos) {
3431             return true;
3432         }
3433 
3434         startpos = pos + 1;
3435     }
3436 }
3437 
3438 //////////////////// goto sectioning command ////////////////////
3439 
gotoNextSectioning()3440 void EditorExtension::gotoNextSectioning()
3441 {
3442     gotoSectioning(false);
3443 }
3444 
gotoPrevSectioning()3445 void EditorExtension::gotoPrevSectioning()
3446 {
3447     gotoSectioning(true);
3448 }
3449 
gotoSectioning(bool backwards,KTextEditor::View * view)3450 void EditorExtension::gotoSectioning(bool backwards, KTextEditor::View *view)
3451 {
3452     view = determineView(view);
3453     if(!view) {
3454         return;
3455     }
3456 
3457     int rowFound, colFound;
3458     if( view && view->document()->isModified() ) { // after saving, the document structure is the current one, so in this case we don't need to update it
3459         m_ki->viewManager()->updateStructure(true);
3460     }
3461     if(m_ki->structureWidget()->findSectioning(Q_NULLPTR,view->document(), view->cursorPosition().line(), view->cursorPosition().column(), backwards, false, rowFound, colFound)) {
3462         view->setCursorPosition(KTextEditor::Cursor(rowFound, colFound));
3463     }
3464 }
3465 
3466 //////////////////// sectioning popup ////////////////////
3467 
sectioningCommand(KileWidget::StructureViewItem * item,int id)3468 void EditorExtension::sectioningCommand(KileWidget::StructureViewItem *item, int id)
3469 {
3470     KTextEditor::View *view = determineView(Q_NULLPTR);
3471     if(!view) {
3472         return;
3473     }
3474 
3475     if(!item) {
3476         return;
3477     }
3478     KTextEditor::Document *doc = view->document();
3479 
3480     // try to determine the whole secting
3481     // get the start auf the selected sectioning
3482     int row, col, row1, col1, row2, col2;
3483     row = row1 = item->startline() - 1;
3484     col = col1 = item->startcol() - 1;
3485 
3486     // FIXME tbraun make this more clever, introdoce in kiledocinfo a flag which can be easily queried for that, so that we don'
3487     // check, if the document was changed in the meantime
3488     QRegExp reg( "\\\\(part|chapter|section|subsection|subsubsection|paragraph|subparagraph)\\*?\\s*(\\{|\\[)" );
3489     QString textline = getTextLineReal(doc,row1);
3490     if(reg.indexIn(textline, col1) != col1) {
3491         m_ki->errorHandler()->clearMessages();
3492         m_ki->errorHandler()->printMessage(KileTool::Error,
3493                                            i18n("The document was modified and the structure view should be updated, before starting such an operation."),
3494                                            i18n("Structure View Error") );
3495         return;
3496     }
3497 
3498     // increase cursor position and find the following sectioning command
3499     if(!increaseCursorPosition(doc, row, col)) {
3500         return;
3501     }
3502     if (!m_ki->structureWidget()->findSectioning(item, doc, row, col, false, true, row2, col2)) {
3503         // or the end of the document
3504         // if there is a '\end{document} command, we have to exclude it
3505         if (!findEndOfDocument(doc, row, col, row2, col2)) {
3506             row2 = doc->lines() - 1;
3507             col2 = 0;
3508         }
3509     }
3510 
3511     // clear selection and make cursor position visible
3512     view->removeSelection();
3513     view->setCursorPosition(KTextEditor::Cursor(row1, col1));
3514 
3515     QString text;
3516     KTextEditor::Document::EditingTransaction transaction(doc);
3517     switch (id) {
3518     case KileWidget::StructureWidget::SectioningCut:
3519         QApplication::clipboard()->setText(doc->text(KTextEditor::Range(row1, col1, row2, col2)));  // copy -> clipboard
3520         doc->removeText(KTextEditor::Range(row1, col1, row2, col2));                                  // delete
3521         break;
3522     case KileWidget::StructureWidget::SectioningCopy:
3523         QApplication::clipboard()->setText(doc->text(KTextEditor::Range(row1, col1, row2, col2)));  // copy -> clipboard
3524         break;
3525     case KileWidget::StructureWidget::SectioningPaste:
3526         text = QApplication::clipboard()->text();                              // clipboard -> text
3527         if(!text.isEmpty()) {
3528             view->setCursorPosition(KTextEditor::Cursor(row2, col2));                             // insert
3529             view->insertText(text + '\n');
3530         }
3531         break;
3532     case KileWidget::StructureWidget::SectioningSelect:
3533         view->setSelection(KTextEditor::Range(row1, col1, row2, col2));                                // select
3534         break;
3535     case KileWidget::StructureWidget::SectioningDelete:
3536         doc->removeText(KTextEditor::Range(row1, col1, row2, col2));                                  // delete
3537         break;
3538     case KileWidget::StructureWidget::SectioningComment:
3539         commentLaTeX(doc, KTextEditor::Range(row1, col1, row2, col2));
3540         break;
3541     case KileWidget::StructureWidget::SectioningPreview:
3542         view->setSelection(KTextEditor::Range(row1, col1, row2, col2));                               // quick preview
3543         m_ki->quickPreview()->previewSelection(view, false);
3544         view->removeSelection();
3545         break;
3546     }
3547     transaction.finish();
3548 
3549     // update structure view, because it has changed
3550     if(id == KileWidget::StructureWidget::SectioningDelete || id == KileWidget::StructureWidget::SectioningComment) {
3551         m_ki->viewManager()->updateStructure(true);
3552     }
3553 
3554 }
3555 
findEndOfDocument(KTextEditor::Document * doc,int row,int col,int & rowFound,int & colFound)3556 bool EditorExtension::findEndOfDocument(KTextEditor::Document *doc, int row, int col, int &rowFound, int &colFound)
3557 {
3558     KTextEditor::Range documentRange(KTextEditor::Cursor(row, col), doc->documentEnd());
3559     QVector<KTextEditor::Range> foundRanges = doc->searchText(documentRange, "\\end{document}");
3560 
3561     if(foundRanges.size() >= 1) {
3562         KTextEditor::Range range = foundRanges.first();
3563         if(range.isValid()) {
3564             rowFound = range.start().line();
3565             colFound = range.start().column();
3566             return true;
3567         }
3568     }
3569 
3570     return false;
3571 }
3572 
extractIndentationString(KTextEditor::View * view,int line)3573 QString EditorExtension::extractIndentationString(KTextEditor::View *view, int line)
3574 {
3575     KTextEditor::Document* doc = view->document();
3576 
3577     if(!doc) {
3578         return QString();
3579     }
3580 
3581     const QString lineString = doc->line(line);
3582     int lastWhiteSpaceCharIndex = -1;
3583 
3584     for(int i = 0; i < lineString.length(); ++i) {
3585         if(!lineString[i].isSpace()) {
3586             break;
3587         }
3588         ++lastWhiteSpaceCharIndex;
3589     }
3590     return lineString.left(lastWhiteSpaceCharIndex + 1);
3591 }
3592 
3593 }
3594 
3595