kde-extraapps/kdevelop/languages/cpp/codecompletion/model.cpp

253 lines
8.7 KiB
C++
Raw Normal View History

/*
* KDevelop C++ Code Completion Support
*
* Copyright 2006-2007 Hamish Rodda <rodda@kde.org>
* Copyright 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
*
* 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 <kdebug.h>
#include <ktexteditor/view.h>
#include <ktexteditor/document.h>
#include <ktexteditor/codecompletioninterface.h>
#include <language/codecompletion/codecompletioncontext.h>
#include <language/backgroundparser/backgroundparser.h>
#include <interfaces/icore.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/idocumentcontroller.h>
#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<KTextEditor::CodeCompletionInterface*>(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<Cpp::CodeCompletionContext*>(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<KDevelop::CompletionCustomGroupNode*>(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<CompletionTreeElementPointer> item, KDevelop::CodeCompletionContext::Ptr completionContext)
{
TypeConversion::startCache();
KDevelop::CodeCompletionModel::foundDeclarations(item, completionContext);
}
}
#include "moc_model.cpp"