/* * KDevelop C++ Code Completion Support * * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "model.h" #include #include #include #include #include #include #include #include #include #include "worker.h" #include "context.h" #include "../cppduchain/typeconversion.h" using namespace KTextEditor; using namespace KDevelop; namespace Cpp { CodeCompletionModel* CodeCompletionModel::s_self = nullptr; bool useArgumentHintInAutomaticCompletion() { return false; } CodeCompletionModel::CodeCompletionModel( QObject * parent ) : KDevelop::CodeCompletionModel(parent) { setForceWaitForModel(true); Q_ASSERT(!s_self); s_self = this; connect(ICore::self()->languageController()->backgroundParser(), SIGNAL(parseJobFinished(KDevelop::ParseJob*)), this, SLOT(parseJobFinished(KDevelop::ParseJob*))); } CodeCompletionModel::~CodeCompletionModel() { s_self = nullptr; } CodeCompletionModel* CodeCompletionModel::self() { return s_self; } void CodeCompletionModel::startCompletionAfterParsing(const IndexedString& path) { m_awaitDocument = path; } void CodeCompletionModel::parseJobFinished(ParseJob* job) { if (job->document() != m_awaitDocument || ICore::self()->languageController()->backgroundParser()->isQueued(m_awaitDocument)) { return; } IDocument* doc = ICore::self()->documentController()->documentForUrl(m_awaitDocument.toUrl()); m_awaitDocument = {}; if (!doc || !doc->textDocument()) { return; } auto view = doc->textDocument()->activeView(); if (!view || !view->hasFocus()) { return; } auto iface = dynamic_cast(view); if (iface) { ///@todo 1. This is a non-public interface, and 2. Completion should be started in "automatic invocation" mode QMetaObject::invokeMethod(view, "userInvokedCompletion"); } } bool CodeCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString& inserted, bool userInsertion, const KTextEditor::Cursor& position) { kDebug() << inserted; QString insertedTrimmed = inserted.trimmed(); TypeConversion::startCache(); QString lineText = view->document()->text(KTextEditor::Range(position.line(), 0, position.line(), position.column())); if(lineText.startsWith("#") && lineText.contains("include") && inserted.endsWith("/")) return true; //Directory-content completion if(insertedTrimmed.endsWith('\"')) return false; //Never start completion behind a string literal if(useArgumentHintInAutomaticCompletion()) if(insertedTrimmed.endsWith( '(' ) || insertedTrimmed.endsWith(',') || insertedTrimmed.endsWith('<') || insertedTrimmed.endsWith(":") ) return true; //Start automatic completion behind '::' if(insertedTrimmed.endsWith(":") && position.column() > 1 && lineText.right(2) == "::") return true; return KDevelop::CodeCompletionModel::shouldStartCompletion(view, inserted, userInsertion, position); } void CodeCompletionModel::aborted(KTextEditor::View* view) { kDebug() << "aborting"; worker()->abortCurrentCompletion(); TypeConversion::stopCache(); KDevelop::CodeCompletionModel::aborted(view); } bool isValidIncludeDirectiveCharacter(QChar character) { return character.isLetterOrNumber() || character == '_' || character == '-' || character == '.'; } bool CodeCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion) { if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end()) return true; //Always abort when the completion-range has been left //Do not abort completions when the text has been empty already before and a newline has been entered QString line = view->document()->line(range.start().line()).trimmed(); if(line.startsWith("#include")) { //Do custom check for include directives, since we allow more character then during usual completion QString text = view->document()->text(range); for(int a = 0; a < text.length(); ++a) { if(!isValidIncludeDirectiveCharacter(text[a])) return true; } return false; } static const QRegExp allowedText("^\\~?(\\w*)"); return !allowedText.exactMatch(currentCompletion); } KDevelop::CodeCompletionWorker* CodeCompletionModel::createCompletionWorker() { return new CodeCompletionWorker(this); } Range CodeCompletionModel::updateCompletionRange(View* view, const KTextEditor::Range& range) { if(completionContext()) { Cpp::CodeCompletionContext* cppContext = dynamic_cast(completionContext().data()); Q_ASSERT(cppContext); cppContext->setFollowingText(view->document()->text(range)); bool didReset = false; if(completionContext()->ungroupedElements().size()) { //Update the ungrouped elements, since they may have changed their text int row = rowCount() - completionContext()->ungroupedElements().size(); foreach(KDevelop::CompletionTreeElementPointer item, completionContext()->ungroupedElements()) { QModelIndex parent = index(row, 0); KDevelop::CompletionCustomGroupNode* group = dynamic_cast(item.data()); if(group) { int subRow = 0; foreach(KDevelop::CompletionTreeElementPointer item, group->children) { if(item->asItem() && item->asItem()->dataChangedWithInput()) { // dataChanged(index(subRow, Name, parent), index(subRow, Name, parent)); kDebug() << "doing dataChanged"; reset(); ///@todo This is very expensive, but kate doesn't listen for dataChanged(..). Find a cheaper way to achieve this. didReset = true; break; } ++subRow; } } if(didReset) break; if(item->asItem() && item->asItem()->dataChangedWithInput()) { reset(); didReset = true; break; } ++row; } // dataChanged(index(rowCount() - completionContext()->ungroupedElements().size(), 0), index(rowCount()-1, columnCount()-1 )); } } QString line = view->document()->line(range.start().line()).trimmed(); if(line.startsWith("#include")) { //Skip over all characters that are allowed in a filename but usually not in code-completion KTextEditor::Range newRange = range; while(newRange.start().column() > 0) { KTextEditor::Cursor newStart = newRange.start(); newStart.setColumn(newStart.column()-1); QChar character = view->document()->character(newStart); if(isValidIncludeDirectiveCharacter(character)) { newRange.start() = newStart; //Skip }else{ break; } } kDebug() << "new range:" << newRange; return newRange; } return KDevelop::CodeCompletionModel::updateCompletionRange(view, range); } Range CodeCompletionModel::completionRange(View* view, const KTextEditor::Cursor& position) { Range range = KDevelop::CodeCompletionModel::completionRange(view, position); if (range.start().column() > 0) { KTextEditor::Range preRange(Cursor(range.start().line(), range.start().column() - 1), Cursor(range.start().line(), range.start().column())); const QString contents = view->document()->text(preRange); if ( contents == "~" ) { range.expandToRange(preRange); } } return range; } void CodeCompletionModel::foundDeclarations(QList item, KDevelop::CodeCompletionContext::Ptr completionContext) { TypeConversion::startCache(); KDevelop::CodeCompletionModel::foundDeclarations(item, completionContext); } } #include "moc_model.cpp"