File indexing completed on 2024-04-28 13:00:35
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 ¤tCompletion) 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 ¤tCompletion) 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