File indexing completed on 2024-04-14 15:17:27

0001 /**********************************************************************************************
0002   Copyright (C) 2004-2007 by Holger Danielsson (holger.danielsson@versanet.de)
0003                 2008-2016 by Michel Ludwig (michel.ludwig@kdemail.net)
0004 ***********************************************************************************************/
0005 
0006 /***************************************************************************
0007  *                                                                         *
0008  *   This program is free software; you can redistribute it and/or modify  *
0009  *   it under the terms of the GNU General Public License as published by  *
0010  *   the Free Software Foundation; either version 2 of the License, or     *
0011  *   (at your option) any later version.                                   *
0012  *                                                                         *
0013  ***************************************************************************/
0014 
0015 #include "codecompletion.h"
0016 
0017 #include <algorithm>
0018 
0019 #include <QFile>
0020 #include <QList>
0021 #include <QRegularExpression>
0022 #include <QRegExp>
0023 #include <QTimer>
0024 
0025 #include <KConfig>
0026 #include <KLocalizedString>
0027 #include <KTextEditor/Cursor>
0028 
0029 #include "kiledebug.h"
0030 #include "abbreviationmanager.h"
0031 #include "documentinfo.h"
0032 #include "editorextension.h"
0033 #include "kiledocmanager.h"
0034 #include "kileinfo.h"
0035 #include "kileviewmanager.h"
0036 #include "kileconfig.h"
0037 #include "utilities.h"
0038 
0039 namespace KileCodeCompletion {
0040 
0041 LaTeXCompletionModel::LaTeXCompletionModel(QObject *parent, KileCodeCompletion::Manager *manager,
0042         KileDocument::EditorExtension *editorExtension)
0043     : KTextEditor::CodeCompletionModel(parent), m_codeCompletionManager(manager), m_editorExtension(editorExtension), m_currentView(Q_NULLPTR)
0044 {
0045     setHasGroups(false);
0046 }
0047 
0048 LaTeXCompletionModel::~LaTeXCompletionModel()
0049 {
0050 }
0051 
0052 void LaTeXCompletionModel::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range,
0053         InvocationType invocationType)
0054 {
0055     if(!range.isValid()
0056             || (invocationType == AutomaticInvocation && !KileConfig::completeAuto())) {
0057         beginResetModel();
0058         m_completionList.clear();
0059         endResetModel();
0060         return;
0061     }
0062     Q_UNUSED(invocationType);
0063     m_currentView = view;
0064     KILE_DEBUG_CODECOMPLETION << "building model...";
0065     buildModel(view, range);
0066 }
0067 
0068 KTextEditor::Range LaTeXCompletionModel::updateCompletionRange(KTextEditor::View *view,
0069         const KTextEditor::Range &range)
0070 {
0071     KILE_DEBUG_CODECOMPLETION << "updating model..." << view << range;
0072     KTextEditor::Range newRange = completionRange(view, view->cursorPosition());
0073     if(newRange.isValid()) {
0074         buildModel(view, newRange);
0075     }
0076     return newRange;
0077 }
0078 
0079 static inline bool isSpecialLaTeXCommandCharacter(const QChar& c) {
0080     return (c == '{' || c == '[' || c == '*' || c == ']' || c == '}');
0081 }
0082 
0083 static inline int specialLaTeXCommandCharacterOrdering(const QChar& c)
0084 {
0085     switch(c.unicode()) {
0086     case '{':
0087         return 1;
0088     case '[':
0089         return 2;
0090     case ']':
0091         return 3;
0092     case '}':
0093         return 4;
0094     case '*':
0095         return 5;
0096     default: // does nothing
0097         break;
0098     }
0099     return 4; // must be 'isLetterOrNumber()' now
0100 }
0101 
0102 static bool laTeXCommandLessThan(const QString& s1, const QString& s2)
0103 {
0104     for(int i = 0; i < s1.length(); ++i) {
0105         if(i >= s2.length()) {
0106             return false;
0107         }
0108         const QChar c1 = s1.at(i);
0109         const QChar c2 = s2.at(i);
0110 
0111         if(c1 == c2) {
0112             continue;
0113         }
0114         if(c1.isLetterOrNumber()) {
0115             if(isSpecialLaTeXCommandCharacter(c2)) {
0116                 return false;
0117             }
0118             else {
0119                 return (c1 < c2);
0120             }
0121         }
0122         else if(isSpecialLaTeXCommandCharacter(c1)) {
0123             if(isSpecialLaTeXCommandCharacter(c2)) {
0124                 return (specialLaTeXCommandCharacterOrdering(c1)
0125                         < specialLaTeXCommandCharacterOrdering(c2));
0126             }
0127             else if(c2.isLetterOrNumber()) {
0128                 return true;
0129             }
0130             else {
0131                 return (c1 < c2);
0132             }
0133         }
0134     }
0135     return true;
0136 }
0137 
0138 void LaTeXCompletionModel::buildModel(KTextEditor::View *view, const KTextEditor::Range &range)
0139 {
0140     QString completionString = view->document()->text(range);
0141     KILE_DEBUG_CODECOMPLETION << "Text in completion range: " << completionString;
0142     m_completionList.clear();
0143 
0144     if(completionString.startsWith('\\')) {
0145         m_completionList = m_codeCompletionManager->getLaTeXCommands();
0146         m_completionList += m_codeCompletionManager->getLocallyDefinedLaTeXCommands(view);
0147     }
0148     else {
0149         KTextEditor::Cursor latexCommandStart = determineLaTeXCommandStart(view->document(),
0150                                                 view->cursorPosition());
0151         if(!latexCommandStart.isValid()) {
0152             return;
0153         }
0154         QString leftSubstring = view->document()->text(KTextEditor::Range(latexCommandStart,
0155                                 view->cursorPosition()));
0156         // check whether we are supposed to build a model for reference or citation completion
0157         int citationIndex = leftSubstring.indexOf(m_codeCompletionManager->m_citeRegExp);
0158         int referenceIndex = leftSubstring.indexOf(m_codeCompletionManager->m_referencesRegExp);
0159         if(referenceIndex != -1) {
0160             //FIXME: the retrieval of labels and BibTeX entries has to be revised!
0161             m_completionList = m_codeCompletionManager->m_ki->allLabels();
0162         }
0163         else if(citationIndex != -1) {
0164             m_completionList = m_codeCompletionManager->m_ki->allBibItems();
0165         }
0166     }
0167     beginResetModel();
0168     filterModel(completionString);
0169     std::sort(m_completionList.begin(), m_completionList.end(), laTeXCommandLessThan);
0170     endResetModel();
0171 }
0172 
0173 KTextEditor::Cursor LaTeXCompletionModel::determineLaTeXCommandStart(KTextEditor::Document *doc,
0174         const KTextEditor::Cursor& position) const
0175 {
0176     QString line = doc->line(position.line());
0177 //  QRegExp completionStartRegExp("((\\s|^)?)((\\\\\\w*)|(\\w+))$");
0178 //  QRegExp completionStartRegExp("((\\\\\\w*)|([^\\\\]\\b\\w+))$");
0179 //  QRegExp completionStartRegExp("(\\\\\\w*)[^\\\\]*$");
0180 
0181     // TeX allows '.' characters inside citation labels (bug 266670)
0182     QRegExp completionStartRegExp("(\\\\([\\s\\{\\}\\[\\]\\w,.=\"'~:]|(\\&)|(\\$)|(\\%)(\\#)(\\_)|(\\{)|(\\})|(\\backslash)|(\\^)|(\\[)|(\\]))*)$");
0183     completionStartRegExp.setMinimal(true);
0184     QString leftSubstring = line.left(position.column());
0185     KILE_DEBUG_CODECOMPLETION << "leftSubstring: " << leftSubstring;
0186     int startPos = completionStartRegExp.lastIndexIn(leftSubstring);
0187     if(startPos >= 0) {
0188         return KTextEditor::Cursor(position.line(), startPos);
0189     }
0190     else {
0191         return KTextEditor::Cursor::invalid();
0192     }
0193 }
0194 
0195 bool LaTeXCompletionModel::isWithinLaTeXCommand(KTextEditor::Document *doc, const KTextEditor::Cursor& commandStart,
0196         const KTextEditor::Cursor& cursorPosition) const
0197 {
0198     QString commandText = doc->text(KTextEditor::Range(commandStart, cursorPosition));
0199     int numOpenSquareBrackets = commandText.count(QRegularExpression("[^\\\\]\\["));
0200     int numClosedSquareBrackets = commandText.count(QRegularExpression("[^\\\\]\\]"));
0201     int numOpenCurlyBrackets = commandText.count(QRegularExpression("[^\\\\]\\{"));
0202     int numClosedCurlyBrackets = commandText.count(QRegularExpression("[^\\\\]\\}"));
0203     if(numOpenSquareBrackets != numClosedSquareBrackets || numOpenCurlyBrackets != numClosedCurlyBrackets) {
0204         return true;
0205     }
0206     if(numOpenSquareBrackets == 0 && numOpenCurlyBrackets == 0 && commandText.count(' ') == 0) {
0207         return true;
0208     }
0209     return false;
0210 }
0211 
0212 KTextEditor::Range LaTeXCompletionModel::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position)
0213 {
0214     bool latexCompletion = true;
0215     QString line = view->document()->line(position.line());
0216     KTextEditor::Cursor startCursor = position;
0217     KTextEditor::Cursor endCursor = position;
0218 
0219     QRegExp completionEndRegExp("\\W|\\b|\\\\");
0220 
0221     int cursorPos = position.column();
0222 
0223     KTextEditor::Cursor latexCommandStart = determineLaTeXCommandStart(view->document(), position);
0224     KILE_DEBUG_CODECOMPLETION << "LaTeX command start " << latexCommandStart;
0225     if(!latexCommandStart.isValid() || !isWithinLaTeXCommand(view->document(), latexCommandStart, position)) {
0226         return KTextEditor::Range::invalid();
0227     }
0228     QString completionString = view->document()->text(KTextEditor::Range(latexCommandStart,
0229                                position));
0230     KILE_DEBUG_CODECOMPLETION << "completionString " << completionString;
0231     //check whether we are completing a citation of reference
0232     if(completionString.indexOf(m_codeCompletionManager->m_citeRegExp) != -1
0233             || completionString.indexOf(m_codeCompletionManager->m_referencesRegExp) != -1) {
0234         KILE_DEBUG_CODECOMPLETION << "found citation or reference!";
0235         int openBracketIndex = completionString.indexOf('{');
0236         if(openBracketIndex != -1) {
0237             // TeX allows '.' characters inside citation labels (bug 266670)
0238             QRegExp labelListRegExp("\\s*(([:.\\w]+)|([:.\\w]+(\\s*,\\s*[:.\\w]*)+))");
0239             labelListRegExp.setMinimal(false);
0240             int column = openBracketIndex + 1;
0241             KILE_DEBUG_CODECOMPLETION << "open bracket column + 1: " << column;
0242             KILE_DEBUG_CODECOMPLETION << labelListRegExp.indexIn(completionString, openBracketIndex + 1);
0243             if(labelListRegExp.indexIn(completionString, openBracketIndex + 1) == openBracketIndex + 1
0244                     && labelListRegExp.matchedLength() + openBracketIndex + 1 == completionString.length()) {
0245                 QRegExp lastCommaRegExp(",\\s*");
0246                 int lastCommaIndex = lastCommaRegExp.lastIndexIn(completionString);
0247                 if(lastCommaIndex >= 0) {
0248                     KILE_DEBUG_CODECOMPLETION << "last comma found at: " << lastCommaIndex;
0249                     column =  lastCommaIndex + lastCommaRegExp.matchedLength();
0250                 }
0251             }
0252             KILE_DEBUG_CODECOMPLETION << labelListRegExp.errorString();
0253             startCursor.setColumn(latexCommandStart.column() + column);
0254             latexCompletion = false;
0255         }
0256         else {
0257             startCursor = latexCommandStart;
0258         }
0259     }
0260     else {
0261         startCursor = latexCommandStart;
0262     }
0263 
0264     int endPos = line.indexOf(completionEndRegExp, cursorPos);
0265     KILE_DEBUG_CODECOMPLETION << "endPos" << endPos;
0266     if(endPos >= 0) {
0267         endCursor.setColumn(endPos);
0268     }
0269     KTextEditor::Range completionRange(startCursor, endCursor);
0270     int rangeLength = endCursor.column() - startCursor.column();
0271 
0272     if(latexCompletion && KileConfig::completeAuto() && rangeLength < KileConfig::completeAutoThreshold() + 1) { // + 1 for the command backslash
0273         KILE_DEBUG_CODECOMPLETION << "not reached the completion threshold yet";
0274         return KTextEditor::Range::invalid();
0275     }
0276     KILE_DEBUG_CODECOMPLETION << "returning completion range: " << completionRange;
0277     return completionRange;
0278 }
0279 
0280 bool LaTeXCompletionModel::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText,
0281         bool userInsertion, const KTextEditor::Cursor &position)
0282 {
0283     Q_UNUSED(view);
0284     Q_UNUSED(position);
0285     if(!KileConfig::completeAuto()) {
0286         return false;
0287     }
0288 
0289     if(insertedText.isEmpty()) {
0290         return false;
0291     }
0292 
0293     if(insertedText.endsWith('{')) {
0294         return true;
0295     }
0296     else {
0297         return CodeCompletionModelControllerInterface::shouldStartCompletion(view, insertedText, userInsertion, position);
0298     }
0299 }
0300 
0301 bool LaTeXCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range,
0302         const QString &currentCompletion)
0303 {
0304     Q_UNUSED(currentCompletion);
0305     if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end()
0306             || m_completionList.size() == 0) {
0307         return true;
0308     }
0309     return false;
0310 }
0311 
0312 QString LaTeXCompletionModel::filterString(KTextEditor::View *view, const KTextEditor::Range &range,
0313         const KTextEditor::Cursor &position)
0314 {
0315     Q_UNUSED(position);
0316     KILE_DEBUG_CODECOMPLETION << "range: " << range;
0317     KILE_DEBUG_CODECOMPLETION << "text: " << (range.isValid() ? view->document()->text(range)
0318                               : "(invalid range)");
0319 
0320     return "";
0321 }
0322 
0323 QVariant LaTeXCompletionModel::data(const QModelIndex& index, int role) const
0324 {
0325     switch(role) {
0326     case Qt::DisplayRole:
0327         if(index.column() != KTextEditor::CodeCompletionModel::Name) {
0328             return QVariant();
0329         }
0330         return m_completionList.at(index.row());
0331     case InheritanceDepth:
0332         return index.row();
0333     }
0334 
0335     return QVariant();
0336 }
0337 
0338 QModelIndex LaTeXCompletionModel::index(int row, int column, const QModelIndex &parent) const
0339 {
0340     if (row < 0 || row >= m_completionList.count() || column < 0 || column >= ColumnCount || parent.isValid()) {
0341         return QModelIndex();
0342     }
0343 
0344     return createIndex(row, column);
0345 }
0346 
0347 int LaTeXCompletionModel::rowCount(const QModelIndex &parent) const
0348 {
0349     if(parent.isValid()) {
0350         return 0;
0351     }
0352     return m_completionList.size();
0353 }
0354 
0355 void LaTeXCompletionModel::filterModel(const QString& text)
0356 {
0357     QMutableStringListIterator it(m_completionList);
0358     while(it.hasNext()) {
0359         QString string = it.next();
0360         if(!string.startsWith(text)) {
0361             it.remove();
0362         }
0363     }
0364 }
0365 
0366 void LaTeXCompletionModel::executeCompletionItem(KTextEditor::View *view,
0367         const KTextEditor::Range& word, const QModelIndex &index) const
0368 {
0369     KTextEditor::Document *document = view->document();
0370     KTextEditor::Cursor startCursor = word.start();
0371     const static QRegExp reEnv = QRegExp("^\\\\(begin|end)[^a-zA-Z]+");
0372 
0373     int cursorXPos = -1, cursorYPos = -1;
0374     QString completionText = data(index.sibling(index.row(), Name), Qt::DisplayRole).toString();
0375 
0376     QString textToInsert;
0377     int envIndex = reEnv.indexIn(completionText);
0378     if(completionText != "\\begin{}" && envIndex != -1) { // we are completing an environment
0379         QString prefix;
0380         prefix = document->text(KTextEditor::Range(startCursor.line(), 0,
0381                                 startCursor.line(), word.start().column()));
0382         textToInsert = buildEnvironmentCompletedText(completionText, prefix, cursorYPos, cursorXPos);
0383         KILE_DEBUG_CODECOMPLETION << cursorYPos << ", " << cursorXPos;
0384     }
0385     else {
0386         textToInsert = buildRegularCompletedText(stripParameters(completionText), cursorYPos, cursorXPos, true);
0387     }
0388     // if there are brackets present immediately after 'word' (for example, due to auto-bracketing of
0389     // the editor), we still have to remove them
0390     QString replaceText = document->text(word);
0391     const int numberOfOpenSimpleBrackets = replaceText.count('(');
0392     const int numberOfOpenSquareBrackets = replaceText.count('[');
0393     const int numberOfOpenCurlyBrackets = replaceText.count('{');
0394     const int numberOfClosedSimpleBrackets = replaceText.count(')');
0395     const int numberOfClosedSquareBrackets = replaceText.count(']');
0396     const int numberOfClosedCurlyBrackets = replaceText.count('}');
0397     const int numberOfClosedBracketsLeft = (numberOfOpenSimpleBrackets - numberOfClosedSimpleBrackets)
0398                                            + (numberOfOpenSquareBrackets - numberOfClosedSquareBrackets)
0399                                            + (numberOfOpenCurlyBrackets - numberOfClosedCurlyBrackets);
0400     if(numberOfOpenSimpleBrackets >= numberOfClosedSimpleBrackets
0401             && numberOfOpenSquareBrackets >= numberOfClosedSquareBrackets
0402             && numberOfOpenCurlyBrackets >= numberOfClosedCurlyBrackets
0403             && document->lineLength(word.end().line()) >= word.end().column() + numberOfClosedBracketsLeft) {
0404         KTextEditor::Range bracketRange = KTextEditor::Range(word.end(), numberOfClosedBracketsLeft);
0405 
0406         QString bracketText = document->text(bracketRange);
0407         if(bracketText.count(")") == (numberOfOpenSimpleBrackets - numberOfClosedSimpleBrackets)
0408                 && bracketText.count("]") == (numberOfOpenSquareBrackets - numberOfClosedSquareBrackets)
0409                 && bracketText.count("}") == (numberOfOpenCurlyBrackets - numberOfClosedCurlyBrackets)) {
0410             document->removeText(bracketRange);
0411         }
0412     }
0413 
0414     // now do the real completion
0415     document->replaceText(word, textToInsert);
0416     //HACK, but it's impossible to do this otherwise
0417     if(KileConfig::completeCursor() && (cursorXPos > 0 || cursorYPos > 0)
0418             && m_currentView && document->views().contains(m_currentView)) {
0419         m_currentView->setCursorPosition(KTextEditor::Cursor(startCursor.line() + (cursorYPos >= 0 ? cursorYPos : 0),
0420                                          startCursor.column() + (cursorXPos >= 0 ? cursorXPos : 0)));
0421     }
0422 }
0423 
0424 QString LaTeXCompletionModel::filterLatexCommand(const QString &text, int &cursorYPos, int &cursorXPos)
0425 {
0426     const static QRegExp reEnv = QRegExp("^\\\\(begin|end)[^a-zA-Z]+");
0427 
0428     cursorXPos = -1, cursorYPos = -1;
0429     QString textToInsert;
0430     int envIndex = reEnv.indexIn(text);
0431     if(text != "\\begin{}" && envIndex != -1) {
0432         textToInsert = buildEnvironmentCompletedText(text, QString(), cursorYPos, cursorXPos);
0433     }
0434     else {
0435         textToInsert = buildRegularCompletedText(stripParameters(text), cursorYPos, cursorXPos, true);
0436     }
0437     return textToInsert;
0438 }
0439 
0440 // strip all names enclosed in braces
0441 // consider also beamer like stuff [<...>] and <...>
0442 QString LaTeXCompletionModel::stripParameters(const QString &text) const
0443 {
0444     QString s;
0445     bool ignore = false;
0446 
0447     for(int i = 0; i < text.length(); ++i) {
0448         QChar c = text[i];
0449         switch(c.unicode()) {
0450         case '[':
0451         case '{':
0452         case '(':
0453         case '<':
0454             s += c;
0455             ignore = true;
0456             break;
0457         case ']':
0458         case '}':
0459         case ')':
0460         case '>':
0461             s += c;
0462             ignore = false;
0463             break;
0464         case ',':
0465             s += c;
0466             break;
0467         default:
0468             if(!ignore) {
0469                 s += c;
0470             }
0471             break;
0472         }
0473     }
0474     return s;
0475 }
0476 
0477 QString LaTeXCompletionModel::buildRegularCompletedText(const QString &text, int &cursorYPos, int &cursorXPos, bool checkGroup) const
0478 {
0479     bool setCursor = true, setBullets = true;
0480     bool foundgroup = false;
0481     QString s;
0482 
0483     cursorXPos = -1;
0484     cursorYPos = -1;
0485     for(int i = 0; i < text.length(); ++i) {
0486         QChar c = text[i];
0487         switch(c.unicode()) {
0488         case '<':
0489         case '{':
0490         case '(':
0491         case '[': // insert character
0492             s += c;
0493             if(cursorXPos < 0) {
0494                 // remember position after first brace
0495                 if(c == '[' && (i + 1) < text.length() &&  text[i + 1] == '<') {
0496                     cursorXPos = i + 2;
0497                     s += text[i + 1];
0498                     i++;
0499                 }// special handling for '[<'
0500                 else {
0501                     cursorXPos = i + 1;
0502                 }
0503                 // insert bullet, if this is no cursorposition
0504                 if((!setCursor) && setBullets && !(c == '[' && (i + 1) < text.length() &&  text[i + 1] == '<')) {
0505                     s += s_bullet;
0506                 }
0507             }
0508             // insert bullets after following braces
0509             else if(setBullets && !(c == '[' && (i + 1) < text.length() &&  text[i + 1] == '<')) {
0510                 s += s_bullet;
0511             }
0512             break;
0513         case '>':
0514         case '}':
0515         case ')':
0516         case ']':                    // insert character
0517             s += c;
0518             break;
0519         case ',':                    // insert character
0520             s += c;
0521             // insert bullet?
0522             if(setBullets) {
0523                 s += s_bullet;
0524             }
0525             break;
0526         case '.': // if the last character is a point of a range operator,
0527             // it will be replaced by a space or a bullet surrounded by spaces
0528             if(checkGroup && (s.right(1) == ".")) {
0529                 foundgroup = true;
0530                 s.truncate(s.length() - 1);
0531                 if(setBullets) {
0532                     s += ' ' + s_bullet + ' ';
0533                 }
0534                 else {
0535                     s += ' ';
0536                 }
0537             }
0538             else {
0539                 s += c;
0540             }
0541             break;
0542         default:                      // insert all other characters
0543             s += c;
0544             break;
0545         }
0546     }
0547 
0548     // some more work with groups and bullets
0549     if(s.length() >= 2 && checkGroup && foundgroup && (setBullets | setCursor)) {
0550         int pos = 0;
0551 
0552         // search for braces, brackets and parens
0553         switch(s[1].unicode()) {
0554         case 'l':
0555             if(s.left(6) == "\\left ") {
0556                 pos = 5;
0557             }
0558             break;
0559         case 'b':
0560             if(s.left(6) == "\\bigl ") {
0561                 pos = 5;
0562             }
0563             else if(s.left(7) == "\\biggl ") {
0564                 pos = 6;
0565             }
0566             break;
0567         case 'B' :
0568             if(s.left(6) == "\\Bigl ") {
0569                 pos = 5;
0570             }
0571             else if(s.left(7) == "\\Biggl ") {
0572                 pos = 6;
0573             }
0574             break;
0575         }
0576 
0577         // update cursorposition and set bullet
0578         if(pos > 0) {
0579             if(setCursor) {
0580                 cursorXPos = pos;
0581             }
0582             if(setBullets) {
0583                 if(!setCursor) {
0584                     s.insert(pos, s_bullet);
0585                 }
0586                 s.append(s_bullet);
0587             }
0588         }
0589     }
0590 
0591     return s;
0592 }
0593 
0594 QString LaTeXCompletionModel::buildEnvironmentCompletedText(const QString &text, const QString &prefix,
0595         int &ypos, int &xpos) const
0596 {
0597     static QRegExp reEnv = QRegExp("^\\\\(begin|end)\\{([^\\}]*)\\}([^\\\\]*)(.*)");
0598 
0599     if(reEnv.indexIn(text) == -1) {
0600         return text;
0601     }
0602 
0603     QString parameter = stripParameters(reEnv.cap(3));
0604     QString start = reEnv.cap(1);
0605     QString envname = reEnv.cap(2);
0606     QString remainder = reEnv.cap(4);
0607     QString whitespace = buildWhiteSpaceString(prefix);
0608     QString envIndent = m_editorExtension->autoIndentEnvironment();
0609 
0610     QString s = "\\" + start + "{" + envname + "}" + parameter + "\n";
0611 
0612     s += whitespace;
0613     if(start != "end") {
0614         s += envIndent;
0615     }
0616 
0617     if(!remainder.isEmpty()) {
0618         s += remainder + ' ';
0619     }
0620 
0621     if(KileConfig::completeBullets() && !parameter.isEmpty()) {
0622         s += s_bullet;
0623     }
0624 
0625     if(KileConfig::completeCloseEnv() && start != "end") {
0626         s += '\n' + whitespace + "\\end{" + envname + "}\n";
0627     }
0628 
0629     if(parameter.isEmpty()) {
0630         ypos = 1;
0631         xpos = envIndent.length() + ((!remainder.isEmpty()) ? remainder.length() + 1 : 0);
0632     }
0633     else {
0634         ypos = 0;
0635         if(parameter.left(2) == "[<") {
0636             xpos = 10 + envname.length();
0637         }
0638         else {
0639             xpos = 9 + envname.length();
0640         }
0641     }
0642 
0643     return s;
0644 }
0645 
0646 QString LaTeXCompletionModel::buildWhiteSpaceString(const QString &s) const
0647 {
0648     QString whitespace = s;
0649     for(int i = 0; i < whitespace.length(); ++i) {
0650         if(!whitespace[i].isSpace()) {
0651             whitespace[i] = ' ';
0652         }
0653     }
0654     return whitespace;
0655 }
0656 
0657 AbbreviationCompletionModel::AbbreviationCompletionModel(QObject *parent, KileAbbreviation::Manager *manager)
0658     : KTextEditor::CodeCompletionModel(parent), m_abbreviationManager(manager)
0659 {
0660     setHasGroups(false);
0661 }
0662 
0663 AbbreviationCompletionModel::~AbbreviationCompletionModel()
0664 {
0665 }
0666 
0667 QModelIndex AbbreviationCompletionModel::index(int row, int column, const QModelIndex &parent) const
0668 {
0669     if (row < 0 || row >= m_completionList.count() || column < 0 || column >= ColumnCount || parent.isValid()) {
0670         return QModelIndex();
0671     }
0672 
0673     return createIndex(row, column);
0674 }
0675 
0676 QVariant AbbreviationCompletionModel::data(const QModelIndex& index, int role) const
0677 {
0678     if(index.column() != KTextEditor::CodeCompletionModel::Name) {
0679         return QVariant();
0680     }
0681     switch(role) {
0682     case Qt::DisplayRole:
0683         return m_completionList.at(index.row());
0684     }
0685 
0686     return QVariant();
0687 }
0688 
0689 int AbbreviationCompletionModel::rowCount(const QModelIndex &parent) const
0690 {
0691     if(parent.isValid()) {
0692         return 0;
0693     }
0694     return m_completionList.size();
0695 }
0696 
0697 bool AbbreviationCompletionModel::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText,
0698         bool userInsertion, const KTextEditor::Cursor &position)
0699 {
0700     Q_UNUSED(view);
0701     Q_UNUSED(userInsertion);
0702     Q_UNUSED(position);
0703 
0704     int len = insertedText.length();
0705     QRegExp whitespace(" |\t");
0706     whitespace.setMinimal(true);
0707     int pos = insertedText.lastIndexOf(whitespace, -1);
0708     // 'pos' is less than or equal to 'len - 1'
0709     QString searchText = (pos >= 0 && pos < len) ? insertedText.right(len - pos - 1) : insertedText;
0710 
0711     return (KileConfig::completeAutoAbbrev() && m_abbreviationManager->abbreviationStartsWith(searchText));
0712 }
0713 
0714 bool AbbreviationCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range,
0715         const QString &currentCompletion)
0716 {
0717     Q_UNUSED(currentCompletion);
0718     if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end()
0719             || m_completionList.size() == 0) {
0720         return true;
0721     }
0722     return false;
0723 }
0724 
0725 void AbbreviationCompletionModel::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range,
0726         InvocationType invocationType)
0727 {
0728     if(!range.isValid()
0729             || (invocationType == AutomaticInvocation && !KileConfig::completeAutoAbbrev())) {
0730         beginResetModel();
0731         m_completionList.clear();
0732         endResetModel();
0733         return;
0734     }
0735     KILE_DEBUG_CODECOMPLETION << "building model...";
0736     buildModel(view, range, (invocationType == UserInvocation || invocationType == ManualInvocation));
0737 }
0738 
0739 KTextEditor::Range AbbreviationCompletionModel::updateCompletionRange(KTextEditor::View *view,
0740         const KTextEditor::Range &range)
0741 {
0742     if(!range.isValid()) {
0743         beginResetModel();
0744         m_completionList.clear();
0745         endResetModel();
0746         return range;
0747     }
0748 
0749     KILE_DEBUG_CODECOMPLETION << "updating model...";
0750     KTextEditor::Range newRange = completionRange(view, view->cursorPosition());
0751     if(newRange.isValid()) {
0752         buildModel(view, newRange);
0753     }
0754     return newRange;
0755 }
0756 
0757 KTextEditor::Range AbbreviationCompletionModel::completionRange(KTextEditor::View *view,
0758         const KTextEditor::Cursor &position)
0759 {
0760     QString insertedText = view->document()->line(position.line()).left(position.column());
0761     int len = insertedText.length();
0762 
0763     QRegExp whitespace(" |\t");
0764     whitespace.setMinimal(true);
0765     int pos = insertedText.lastIndexOf(whitespace,-1);
0766     QString searchText = (pos>=0 && pos<len-2) ? insertedText.right(len-pos-1) : insertedText;
0767     pos++;
0768 
0769     return KTextEditor::Range( position.line(), pos, position.line(),position.column() );
0770 }
0771 
0772 QString AbbreviationCompletionModel::filterString(KTextEditor::View *view,
0773         const KTextEditor::Range &range,
0774         const KTextEditor::Cursor &position)
0775 {
0776     Q_UNUSED(view);
0777     Q_UNUSED(range);
0778     Q_UNUSED(position);
0779     return "";
0780 }
0781 
0782 void AbbreviationCompletionModel::executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range& word,
0783         const QModelIndex &index) const
0784 {
0785     // replace abbreviation and take care of newlines
0786     QString completionText = data(index.sibling(index.row(), Name), Qt::DisplayRole).toString();
0787     completionText.replace("%n","\n");
0788     KTextEditor::Document *document = view->document();
0789     document->replaceText(word, completionText);
0790 
0791     // look if there is a %C-wish to place the cursor
0792     if (completionText.indexOf("%C") >= 0) {
0793         KTextEditor::Range searchrange = KTextEditor::Range(word.start(),document->lines()+1,0);
0794         QVector<KTextEditor::Range> rangevec = document->searchText(searchrange,"%C");
0795         if (rangevec.size() >= 1) {
0796             KTextEditor::Range range = rangevec.at(0);
0797             document->removeText(range);
0798             if (view) {
0799                 view->setCursorPosition(range.start());
0800             }
0801         }
0802     }
0803 }
0804 
0805 void AbbreviationCompletionModel::buildModel(KTextEditor::View *view, const KTextEditor::Range &range,
0806         bool singleMatchMode)
0807 {
0808     beginResetModel();
0809     m_completionList.clear();
0810     endResetModel();
0811     QString text = view->document()->text(range);
0812     KILE_DEBUG_CODECOMPLETION << text;
0813     if(text.isEmpty()) {
0814         return;
0815     }
0816     if(singleMatchMode && m_abbreviationManager->isAbbreviationDefined(text)) {
0817         m_completionList << m_abbreviationManager->getAbbreviationTextMatch(text);
0818         executeCompletionItem(view, range, index(0, 0));
0819     }
0820     else {
0821         m_completionList = m_abbreviationManager->getAbbreviationTextMatches(text);
0822         m_completionList.sort();
0823         if(m_completionList.size() == 1
0824                 && m_abbreviationManager->isAbbreviationDefined(text)) {
0825             executeCompletionItem(view, range, index(0, 0));
0826         }
0827     }
0828 }
0829 
0830 Manager::Manager(KileInfo *info, QObject *parent)
0831     : QObject(parent), m_ki(info)
0832 {
0833     m_firstConfig = true;
0834 }
0835 
0836 Manager::~Manager()
0837 {
0838 }
0839 
0840 QStringList Manager::getLaTeXCommands() const
0841 {
0842     return m_texWordList;
0843 }
0844 
0845 QStringList Manager::getLocallyDefinedLaTeXCommands(KTextEditor::View *view) const
0846 {
0847     //FIXME: the retrieval of these commands has to be revised!
0848     KileDocument::TextInfo *textInfo = m_ki->docManager()->textInfoFor(view->document());
0849     if(!textInfo) {
0850         return QStringList();
0851     }
0852     return m_ki->allNewCommands(textInfo);
0853 }
0854 
0855 void Manager::readConfig(KConfig *config)
0856 {
0857     Q_UNUSED(config);
0858     KILE_DEBUG_CODECOMPLETION << "======================";
0859 
0860     // reading the wordlists is only necessary at the first start
0861     // and when the list of files changes
0862     if(m_firstConfig || KileConfig::completeChangedLists() || KileConfig::completeChangedCommands()) {
0863         KILE_DEBUG_CODECOMPLETION << "   setting regexp for references...";
0864         buildReferenceCitationRegularExpressions();
0865 
0866         KILE_DEBUG_CODECOMPLETION << "   read wordlists...";
0867         // wordlists for Tex/Latex mode
0868         QStringList files = KileConfig::completeTex();
0869         m_texWordList = readCWLFiles(files, "tex");
0870         addUserDefinedLaTeXCommands(m_texWordList);
0871 
0872         // wordlist for dictionary mode
0873         files = KileConfig::completeDict();
0874         m_dictWordList = readCWLFiles(files, "dictionary");
0875         m_dictWordList.sort();
0876 
0877         // remember changed lists
0878         // FIXME: remove these hacks
0879         m_firstConfig = false;
0880         KileConfig::setCompleteChangedLists(false);
0881         KileConfig::setCompleteChangedCommands(false);
0882     }
0883 }
0884 
0885 void Manager::startLaTeXCompletion(KTextEditor::View *view)
0886 {
0887     if(!view) {
0888         view = m_ki->viewManager()->currentTextView();
0889         if(!view) {
0890             return;
0891         }
0892     }
0893 
0894     KileDocument::TextInfo *textInfo = m_ki->docManager()->textInfoFor(view->document());
0895     KileDocument::LaTeXInfo *latexInfo = dynamic_cast<KileDocument::LaTeXInfo*>(textInfo);
0896     if(!latexInfo) {
0897         return;
0898     }
0899     latexInfo->startLaTeXCompletion(view);
0900 }
0901 
0902 void Manager::textInserted(KTextEditor::View* view, const KTextEditor::Cursor& /* position */, const QString& text)
0903 {
0904     // auto insert '$' if the user just typed a '$' character
0905     if (KileConfig::autoInsertDollar() && text == "$") {
0906         // code completion seems to be never active, so there is no need to
0907         // check KTextEditor::CodeCompletionInterface::isCompletionActive()
0908         KTextEditor::Cursor currentCursorPos = view->cursorPosition();
0909         view->document()->insertText(currentCursorPos, "$");
0910         view->setCursorPosition(currentCursorPos);
0911     }
0912 }
0913 
0914 
0915 void Manager::startLaTeXEnvironment(KTextEditor::View *view)
0916 {
0917     if(!view) {
0918         view = m_ki->viewManager()->currentTextView();
0919         if(!view) {
0920             return;
0921         }
0922     }
0923 
0924     KTextEditor::Cursor cursor = view->cursorPosition();
0925     QString line = view->document()->line(cursor.line()).left(cursor.column());
0926 
0927     QRegExp regexp("\\\\b|\\\\be|\\\\beg|\\\\begi|\\\\begin|\\\\begin\\{|\\\\begin\\{([a-zA-z]*)");
0928     int pos = regexp.lastIndexIn(line);
0929     if(pos >= 0) {
0930         view->document()->replaceText(KTextEditor::Range(cursor.line(), pos, cursor.line(), cursor.column()), "\\begin{"+regexp.cap(1));
0931     }
0932     else {
0933         // environment completion will start with "\begin{en" when the cursor is placed
0934         // after the following strings:
0935         // en
0936         // x=en
0937         // it en
0938         // =en
0939         // it=en
0940         //  en
0941         // but it will start with "\begin{" in the following situations:
0942         // \en
0943         // it\en
0944         // \aen
0945         QRegExp re("(^|[^\\\\A-Za-z])([a-zA-Z]+)$");
0946         pos = re.indexIn(line);
0947         if(pos >= 0) {
0948             view->document()->replaceText(KTextEditor::Range(cursor.line(), re.pos(2), cursor.line(), cursor.column()), "\\begin{" + re.cap(2));
0949         }
0950         else {
0951             view->document()->insertText(cursor, "\\begin{");
0952         }
0953     }
0954 
0955     startLaTeXCompletion(view);
0956 }
0957 
0958 void Manager::startAbbreviationCompletion(KTextEditor::View *view)
0959 {
0960     if(!view) {
0961         view = m_ki->viewManager()->currentTextView();
0962         if(!view) {
0963             return;
0964         }
0965     }
0966 
0967     KileDocument::TextInfo *textInfo = m_ki->docManager()->textInfoFor(view->document());
0968     if(!textInfo) {
0969         return;
0970     }
0971     textInfo->startAbbreviationCompletion(view);
0972 }
0973 
0974 void Manager::buildReferenceCitationRegularExpressions()
0975 {
0976     // build list of references
0977     QString references = getCommandsString(KileDocument::CmdAttrReference);
0978     references.replace('*', "\\*");
0979     m_referencesRegExp.setPattern("^\\\\(" + references + ")\\{");
0980     m_referencesExtRegExp.setPattern("^\\\\(" + references + ")\\{[^\\{\\}\\\\]+,$");
0981 
0982     // build list of citations
0983     QString citations = getCommandsString(KileDocument::CmdAttrCitations);
0984     citations.replace('*',"\\*");
0985     m_citeRegExp.setPattern("^\\\\(((c|C|noc)(ite|itep|itet|itealt|itealp|iteauthor|iteyear|iteyearpar|itetext))" + citations +  ")\\{");
0986     m_citeExtRegExp.setPattern("^\\\\(((c|C|noc)(ite|itep|itet|itealt|itealp|iteauthor|iteyear|iteyearpar|itetext))" + citations + ")\\{[^\\{\\}\\\\]+,$");
0987 }
0988 
0989 QString Manager::getCommandsString(KileDocument::CmdAttribute attrtype)
0990 {
0991     QStringList cmdlist;
0992     QStringList::ConstIterator it;
0993 
0994     // get info about user-defined references
0995     KileDocument::LatexCommands *cmd = m_ki->latexCommands();
0996     cmd->commandList(cmdlist, attrtype, false);
0997 
0998     // build list of references
0999     QString commands;
1000     for(it = cmdlist.constBegin(); it != cmdlist.constEnd(); ++it) {
1001         if(cmd->isStarredEnv(*it) ) {
1002             commands += '|' + (*it).mid(1) + '*';
1003         }
1004         commands += '|' + (*it).mid(1);
1005     }
1006     return commands;
1007 }
1008 
1009 void Manager::addUserDefinedLaTeXCommands(QStringList &wordlist)
1010 {
1011     QStringList cmdlist;
1012     QStringList::ConstIterator it;
1013     KileDocument::LatexCmdAttributes attr;
1014 
1015     // get info about user-defined commands and environments
1016     KileDocument::LatexCommands *cmd = m_ki->latexCommands();
1017     cmd->commandList(cmdlist, KileDocument::CmdAttrNone, true);
1018 
1019     // add entries to wordlist
1020     for(it = cmdlist.constBegin(); it != cmdlist.constEnd(); ++it) {
1021         if(cmd->commandAttributes(*it, attr)) {
1022             QString command,eos;
1023             QStringList entrylist;
1024             if(attr.type < KileDocument::CmdAttrLabel) {         // environment
1025                 command = "\\begin{" + (*it);
1026                 eos = '}';
1027             }
1028             else {                                                   // command
1029                 command = (*it);
1030                 // eos.clear();
1031             }
1032 
1033             // get all possibilities into a stringlist
1034             entrylist.append(command + eos);
1035             if(!attr.option.isEmpty()) {
1036                 entrylist.append(command + eos + "[option]");
1037             }
1038             if(attr.starred) {
1039                 entrylist.append(command + '*' + eos);
1040                 if (!attr.option.isEmpty()) {
1041                     entrylist.append(command + '*' + eos + "[option]");
1042                 }
1043             }
1044 
1045             // finally append entries to wordlist
1046             QStringList::ConstIterator itentry;
1047             for(itentry = entrylist.constBegin(); itentry != entrylist.constEnd(); ++itentry) {
1048                 QString entry = (*itentry);
1049                 if(!attr.parameter.isEmpty()) {
1050                     entry += "{param}";
1051                 }
1052                 if(attr.type == KileDocument::CmdAttrList) {
1053                     entry += "\\item";
1054                 }
1055                 wordlist.append(entry);
1056             }
1057         }
1058     }
1059 }
1060 
1061 QStringList Manager::readCWLFile(const QString &filename, bool fullPathGiven)
1062 {
1063     QStringList toReturn;
1064     QString file = fullPathGiven ? filename : KileUtilities::locate(QStandardPaths::AppDataLocation, "complete/" + filename);
1065     if(file.isEmpty()) {
1066         return toReturn;
1067     }
1068 
1069     QFile f(file);
1070     if(f.open(QIODevice::ReadOnly)) {     // file opened successfully
1071         QTextStream t(&f);         // use a text stream
1072         while(!t.atEnd()) {        // until end of file...
1073             QString s = t.readLine().trimmed();       // line of text excluding '\n'
1074             if(!(s.isEmpty() || s.at(0) == '#')) {
1075                 toReturn.append(s);
1076             }
1077         }
1078         f.close();
1079     }
1080     return toReturn;
1081 }
1082 
1083 QStringList Manager::readCWLFiles(const QStringList &files, const QString &dir)
1084 {
1085 
1086     // read wordlists from files
1087     QStringList wordlist;
1088     for(int i = 0; i < files.count(); ++i) {
1089         QString cwlfile = validCwlFile(files[i]);
1090         if( !cwlfile.isEmpty() ) {
1091             wordlist += readCWLFile(dir + '/' + cwlfile + ".cwl");
1092         }
1093     }
1094     return wordlist;
1095 }
1096 
1097 QString Manager::validCwlFile(const QString &filename)
1098 {
1099     return (filename.at(0) == '1') ? filename.right( filename.length()-2 ) : QString();
1100 }
1101 
1102 
1103 // find local and global cwl files: global files are not added,
1104 // if there is already a local file with this name. We fill a map
1105 // with filename as key and filepath as value.
1106 
1107 static void getCwlFiles(QMap<QString, QString> &map, const QString &dir)
1108 {
1109     QStringList files = QDir(dir, "*.cwl").entryList();
1110     for (QStringList::ConstIterator it = files.constBegin(); it != files.constEnd(); ++it) {
1111         QString filename = QFileInfo(*it).fileName();
1112         if(!map.contains(filename)) {
1113             map[filename] = dir + '/' + (*it);
1114         }
1115     }
1116 }
1117 
1118 QMap<QString, QString> Manager::getAllCwlFiles(const QString &localCwlPath, const QString &globalCwlPath)
1119 {
1120     // get a sorted list of all cwl files from both directories
1121     // Local files are preferred over global ones.
1122     QMap<QString, QString> fileMap;
1123     getCwlFiles(fileMap, localCwlPath);
1124     getCwlFiles(fileMap, globalCwlPath);
1125     return fileMap;
1126 }
1127 
1128 QPair<QString, QString> Manager::getCwlBaseDirs()
1129 {
1130     QString localDir = KileUtilities::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + "complete";
1131     QString globalDir;
1132 
1133     const QStringList dirs = KileUtilities::locateAll(QStandardPaths::AppDataLocation, "complete", QStandardPaths::LocateDirectory);
1134     for(QStringList::ConstIterator it = dirs.constBegin(); it != dirs.constEnd(); ++it) {
1135         if((*it) != localDir) {
1136             globalDir = (*it);
1137             break;
1138         }
1139     }
1140     // we ensure that the directory strings end in '/'
1141     if(!localDir.endsWith('/')) {
1142         localDir += '/';
1143     }
1144     if(!globalDir.endsWith('/')) {
1145         globalDir += '/';
1146     }
1147     return QPair<QString, QString>(localDir, globalDir);
1148 }
1149 
1150 }
1151