void ModelTest::testCompletionRangeSecondLine() { KTextEditor::Document* doc = KTextEditor::Editor::instance()->createDocument(0); doc->setText("body{color:red;}\nbody{font-w:normal;}"); // 01234567890123456789 KTextEditor::View* view = doc->createView(0); CodeCompletionModel* model = new CodeCompletionModel(doc); KTextEditor::Cursor position(1, 9); KTextEditor::Range range = model->completionRange(view, position); kDebug() << range << doc->text(range); QCOMPARE(range, KTextEditor::Range(1, 5, 1, 11)); QCOMPARE(doc->text(range), QString("font-w")); delete doc; }
void FunctionDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { qCDebug(KDEV_PYTHON_CODECOMPLETION) << "FunctionDeclarationCompletionItem executed"; KTextEditor::Document* document = view->document(); auto resolvedDecl = Helper::resolveAliasDeclaration(declaration().data()); DUChainReadLocker lock; auto functionDecl = Helper::functionForCalled(resolvedDecl).declaration; lock.unlock(); if ( ! functionDecl && (! resolvedDecl || ! resolvedDecl->abstractType() || resolvedDecl->abstractType()->whichType() != AbstractType::TypeStructure) ) { qCritical(KDEV_PYTHON_CODECOMPLETION) << "ERROR: could not get declaration data, not executing completion item!"; return; } QString suffix = "()"; KTextEditor::Range checkPrefix(word.start().line(), 0, word.start().line(), word.start().column()); KTextEditor::Range checkSuffix(word.end().line(), word.end().column(), word.end().line(), document->lineLength(word.end().line())); if ( m_doNotCall || document->text(checkSuffix).trimmed().startsWith('(') || document->text(checkPrefix).trimmed().endsWith('@') || (functionDecl && Helper::findDecoratorByName(functionDecl, QLatin1String("property"))) ) { // don't insert brackets if they're already there, // the item is a decorator, or if it's an import item. suffix.clear(); } // place cursor behind bracktes by default int skip = 2; if ( functionDecl ) { bool needsArguments = false; int argumentCount = functionDecl->type<FunctionType>()->arguments().length(); if ( functionDecl->context()->type() == KDevelop::DUContext::Class ) { // it's a member function, so it has the implicit self // TODO static methods needsArguments = argumentCount > 1; } else { // it's a free function needsArguments = argumentCount > 0; } if ( needsArguments ) { // place cursor in brackets if there's parameters skip = 1; } } document->replaceText(word, declaration()->identifier().toString() + suffix); view->setCursorPosition( Cursor(word.end().line(), word.end().column() + skip) ); }
// Scan throughout the entire document for possible completions, // ignoring any dublets const QStringList KateWordCompletionModel::allMatches( KTextEditor::View *view, const KTextEditor::Range &range ) const { KTextEditor::Document *doc = view->document(); QString match_str = doc->text(range); QString s, m; QSet<QString> seen; QStringList l; int i( 0 ); int pos( 0 ); QRegExp re( "\\b(" + match_str + "\\w{1,})" ); while( i < doc->lines() ) { s = doc->line( i ); pos = 0; while ( pos >= 0 ) { pos = re.indexIn( s, pos ); if ( pos >= 0 ) { // typing in the middle of a word if ( ! ( i == range.start().line() && pos == range.start().column() ) ) { m = re.cap( 1 ); if ( ! seen.contains( m ) ) { seen.insert( m ); l << m; } } pos += re.matchedLength(); } } i++; } // Global completion // int db_area = KDebug::registerArea("ktuan-debug"); QMap<QString, QStringList>::const_iterator ci = doc_word_list.constBegin(); while (ci != doc_word_list.constEnd()) { if (ci.key() != doc->url().prettyUrl()) { QStringList list = ci.value(); foreach (QString word, list) { // kDebug(db_area) << "complete word " << word; if (word.startsWith(match_str) && !seen.contains(word)) { // kDebug(db_area) << "Global completion"; seen.insert(word); l << word; } } } ++ci; }
// Scan throughout the entire document for possible completions, // ignoring any dublets const QStringList KateWordCompletionModel::allMatches( KTextEditor::View *view, const KTextEditor::Range &range ) const { QStringList l; int i( 0 ); int pos( 0 ); KTextEditor::Document *doc = view->document(); QRegExp re( "\\b(" + doc->text( range ) + "\\w{1,})" ); QString s, m; QSet<QString> seen; while( i < doc->lines() ) { s = doc->line( i ); pos = 0; while ( pos >= 0 ) { pos = re.indexIn( s, pos ); if ( pos >= 0 ) { // typing in the middle of a word if ( ! ( i == range.start().line() && pos == range.start().column() ) ) { m = re.cap( 1 ); if ( ! seen.contains( m ) ) { seen.insert( m ); l << m; } } pos += re.matchedLength(); } } i++; } return l; }
bool ParseJob::contentsAvailableFromEditor() { KTextEditor::Document* doc = EditorIntegrator::documentForUrl(HashedString(d->document.str())); if (!doc) return false; finaliseChangedRanges(); if (d->revisionToken == -1) { SmartInterface* iface = qobject_cast<SmartInterface*>(doc); if (iface) { QMutexLocker smartLock(iface->smartMutex()); //Here we save the revision d->revisionToken = iface->currentRevision(); iface->useRevision(d->revisionToken); // You must have called contentsAvailableFromEditor, it sets state d->contentsFromEditor = doc->text(); } } return true; }
// Scan throughout the entire document for possible completions, // ignoring any dublets const QStringList KateNewCompletionModel::allMatches( KTextEditor::View *view, const KTextEditor::Range &range ) const { KTextEditor::Document *doc = view->document(); QString match_str = doc->text(range); QString s, m; QSet<QString> seen; QStringList l; int i( 0 ); int pos( 0 ); QRegExp class_rek("([A-Z]\\w*<( |\\w|,|<|>)+>)"); if (match_str.startsWith("new ")) { QString class_name = match_str.mid(4); for (i = 0; i < doc->lines(); ++i) { QString s = doc->line(i); QString m; pos = 0; while (pos >= 0) { pos = class_rek.indexIn(s, pos); if ( pos >= 0 ) { // typing in the middle of a word if ( ! ( i == range.start().line() && pos >= range.start().column() && pos <= range.end().column()) ) { m = class_rek.cap( 1 ); if ( ! seen.contains( m ) && m.startsWith(class_name)) { seen.insert( m ); m = "new " + m; l << m; } } pos += class_rek.matchedLength(); } } } } // convert yieldXXX and Ent::load('XXX') to genXXX and getXXX if (match_str.startsWith("gen") || match_str.startsWith("get")) { QString x = match_str.mid(3); class_rek = QRegExp("(yield|Ent::load\\(\\\'|Ent::load\\(\\\")([A-Z]\\w*)"); for (i = 0; i < doc->lines(); ++i) { QString s = doc->line(i); QString m; pos = 0; while (pos >= 0) { pos = class_rek.indexIn(s, pos); if ( pos >= 0 ) { // typing in the middle of a word if ( ! ( i == range.start().line() && pos >= range.start().column() && pos <= range.end().column()) ) { m = class_rek.cap( 2 ); if ( ! seen.contains( m ) && m.startsWith(x)) { seen.insert( m ); l << ("gen" + m); l << ("get" + m); } } pos += class_rek.matchedLength(); } } } } return l; }
void ImplementationItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { DUChainReadLocker lock(DUChain::lock()); KTextEditor::Document *document = view->document(); QString replText; if (m_declaration) { //TODO:respect custom code styles // get existing modifiers so we can respect the user's choice of public/protected and final QStringList modifiers = getMethodTokens(document->text(KTextEditor::Range(KTextEditor::Cursor::start(), word.start()))); // get range to replace KTextEditor::Range replaceRange(word); if (!modifiers.isEmpty()) { // TODO: is there no easy API to map QString Index to a KTextEditor::Cursor ?! QString methodText = document->text(KTextEditor::Range(KTextEditor::Cursor::start(), word.start())); methodText = methodText.left(methodText.lastIndexOf(modifiers.last(), -1, Qt::CaseInsensitive)); replaceRange.start() = KTextEditor::Cursor(methodText.count('\n'), methodText.length() - methodText.lastIndexOf('\n') - 1); } // get indendation QString indendation; { QString currentLine = document->line(replaceRange.start().line()); indendation = getIndendation(currentLine); if ( !currentLine.isEmpty() && currentLine != indendation ) { // since theres some non-whitespace in this line, skip to the enxt one replText += '\n' + indendation; } if (indendation.isEmpty()) { // use a minimal indendation // TODO: respect code style indendation = QStringLiteral(" "); replText += indendation; } } #if 0 //Disabled, because not everyone writes phpdoc for every function //TODO: move to a phpdoc helper // build phpdoc comment { QualifiedIdentifier parentClassIdentifier; if (DUContext* pctx = m_declaration->context()) { parentClassIdentifier = pctx->localScopeIdentifier(); } else { qCDebug(COMPLETION) << "completion item for implementation has no parent context!"; } replText += "/**\n" + indendation + " * "; // insert old comment: const QString indentationWithExtra = "\n" + indendation + " *"; replText += m_declaration->comment().replace('\n', indentationWithExtra.toAscii().constData()); replText += "\n" + indendation + " * @overload " + m_declaration->internalContext()->scopeIdentifier(true).toString(); replText += "\n" + indendation + " **/\n" + indendation; } #endif // write function signature // copy existing modifiers if (!modifiers.isEmpty()) { // the tokens are in a bad order and there's no reverse method or similar, so we can't simply join the tokens QStringList::const_iterator i = modifiers.constEnd() - 1; while (true) { replText += (*i) + ' '; if (i == modifiers.constBegin()) { break; } else { --i; } } } QString functionName; bool isConstructorOrDestructor = false; bool isInterface = false; if (ClassMemberDeclaration* member = dynamic_cast<ClassMemberDeclaration*>(m_declaration.data())) { // NOTE: it should _never_ be private - but that's the completionmodel / context / worker's job if (!modifiers.contains(QStringLiteral("public")) && !modifiers.contains(QStringLiteral("protected"))) { if (member->accessPolicy() == Declaration::Protected) { replText += QLatin1String("protected "); } else { replText += QLatin1String("public "); } } if (!modifiers.contains(QStringLiteral("static")) && member->isStatic()) { replText += QLatin1String("static "); } functionName = member->identifier().toString(); ClassMethodDeclaration* method = dynamic_cast<ClassMethodDeclaration*>(m_declaration.data()); if (method) { functionName = method->prettyName().str(); isConstructorOrDestructor = method->isConstructor() || method->isDestructor(); } if (member->context() && member->context()->owner()) { ClassDeclaration* classDec = dynamic_cast<ClassDeclaration*>(member->context()->owner()); if (classDec) { isInterface = (classDec->classType() == ClassDeclarationData::Interface); } } } else { qCDebug(COMPLETION) << "completion item for implementation was not a classfunction declaration!"; functionName = m_declaration->identifier().toString(); } if (m_type == ImplementationItem::OverrideVar) { replText += "$" + functionName + " = "; } else { if (!modifiers.contains(QStringLiteral("function"))) { replText += QLatin1String("function "); } replText += functionName; { // get argument list QString arguments; createArgumentList(*this, arguments, 0, true); replText += arguments; } QString arguments; QVector<Declaration*> parameters; if (DUChainUtils::getArgumentContext(m_declaration.data())) parameters = DUChainUtils::getArgumentContext(m_declaration.data())->localDeclarations(); arguments = '('; bool first = true; foreach(Declaration* dec, parameters) { if (first) first = false; else arguments += QLatin1String(", "); arguments += '$' + dec->identifier().toString(); } arguments += ')'; bool voidReturnType = false; if (FunctionType::Ptr::dynamicCast(m_declaration->abstractType())) { AbstractType::Ptr retType = FunctionType::Ptr::staticCast(m_declaration->abstractType())->returnType(); if (retType->equals(new IntegralType(IntegralType::TypeVoid))) { voidReturnType = true; } } replText += QStringLiteral("\n%1{\n%1 ").arg(indendation); if (isInterface || m_type == ImplementationItem::Implement) { } else if (!isConstructorOrDestructor && !voidReturnType) { replText += QStringLiteral("$ret = parent::%2%3;\n%1 return $ret;").arg(indendation, functionName, arguments); } else { replText += QStringLiteral("parent::%1%2;").arg(functionName, arguments); } replText += QStringLiteral("\n%1}\n%1") .arg(indendation); } //TODO: properly place the cursor inside the {} part document->replaceText(replaceRange, replText); } else {
// Do one completion, searching in the desired direction, // if possible void KateWordCompletionView::complete( bool fw ) { KTextEditor::Range r = range(); int inc = fw ? 1 : -1; KTextEditor::Document *doc = m_view->document(); if ( d->dcRange.isValid() ) { //kDebug( 13040 )<<"CONTINUE "<<d->dcRange; // this is a repeted activation // if we are back to where we started, reset. if ( ( fw && d->directionalPos == -1 ) || ( !fw && d->directionalPos == 1 ) ) { const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); if ( spansColumns > 0 ) doc->removeText( *d->liRange ); d->liRange->setRange( KTextEditor::Range::invalid() ); d->dcCursor = r.end(); d->directionalPos = 0; return; } if ( fw ) { const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); d->dcCursor.setColumn( d->dcCursor.column() + spansColumns ); } d->directionalPos += inc; } else // new completion, reset all { //kDebug( 13040 )<<"RESET FOR NEW"; d->dcRange = r; d->liRange->setRange( KTextEditor::Range::invalid() ); d->dcCursor = r.start(); d->directionalPos = inc; d->liRange->setView( m_view ); connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(slotCursorMoved()) ); } d->re.setPattern( "\\b" + doc->text( d->dcRange ) + "(\\w+)" ); int pos ( 0 ); QString ln = doc->line( d->dcCursor.line() ); while ( true ) { //kDebug( 13040 )<<"SEARCHING FOR "<<d->re.pattern()<<" "<<ln<<" at "<<d->dcCursor; pos = fw ? d->re.indexIn( ln, d->dcCursor.column() ) : d->re.lastIndexIn( ln, d->dcCursor.column() ); if ( pos > -1 ) // we matched a word { //kDebug( 13040 )<<"USABLE MATCH"; QString m = d->re.cap( 1 ); if ( m != doc->text( *d->liRange ) && (d->dcCursor.line() != d->dcRange.start().line() || pos != d->dcRange.start().column() ) ) { // we got good a match! replace text and return. d->isCompleting = true; KTextEditor::Range replaceRange(d->liRange->toRange()); if (!replaceRange.isValid()) { replaceRange.setRange(r.end(), r.end()); } doc->replaceText( replaceRange, m ); d->liRange->setRange( KTextEditor::Range( d->dcRange.end(), m.length() ) ); d->dcCursor.setColumn( pos ); // for next try d->isCompleting = false; return; } // equal to last one, continue else { //kDebug( 13040 )<<"SKIPPING, EQUAL MATCH"; d->dcCursor.setColumn( pos ); // for next try if ( fw ) d->dcCursor.setColumn( pos + m.length() ); else { if ( pos == 0 ) { if ( d->dcCursor.line() > 0 ) { int l = d->dcCursor.line() + inc; ln = doc->line( l ); d->dcCursor.setPosition( l, ln.length() ); } else { KNotification::beep(); return; } } else d->dcCursor.setColumn( d->dcCursor.column()-1 ); } } } else // no match { //kDebug( 13040 )<<"NO MATCH"; if ( (! fw && d->dcCursor.line() == 0 ) || ( fw && d->dcCursor.line() >= doc->lines() ) ) { KNotification::beep(); return; } int l = d->dcCursor.line() + inc; ln = doc->line( l ); d->dcCursor.setPosition( l, fw ? 0 : ln.length() ); } } // while true }