Kate
katewordcompletion.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries and the Kate part. 00002 * 00003 * Copyright (C) 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> 00004 * Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org> 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2 of the License, or (at your option) any later version. 00010 * 00011 * This library is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 * Library General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Library General Public License 00017 * along with this library; see the file COPYING.LIB. If not, write to 00018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 * Boston, MA 02110-1301, USA. 00020 */ 00021 00022 //BEGIN includes 00023 #include "katewordcompletion.h" 00024 #include "kateview.h" 00025 #include "kateconfig.h" 00026 #include "katedocument.h" 00027 #include "kateglobal.h" 00028 00029 #include <ktexteditor/variableinterface.h> 00030 #include <ktexteditor/movingrange.h> 00031 00032 #include <kconfig.h> 00033 #include <kdialog.h> 00034 #include <kpluginfactory.h> 00035 #include <klocale.h> 00036 #include <kaction.h> 00037 #include <kactioncollection.h> 00038 #include <knotification.h> 00039 #include <kparts/part.h> 00040 #include <kiconloader.h> 00041 #include <kpagedialog.h> 00042 #include <kpagewidgetmodel.h> 00043 #include <ktoggleaction.h> 00044 #include <kconfiggroup.h> 00045 #include <kcolorscheme.h> 00046 #include <kaboutdata.h> 00047 00048 #include <QtCore/QRegExp> 00049 #include <QtCore/QString> 00050 #include <QtCore/QSet> 00051 #include <QtGui/QSpinBox> 00052 #include <QtGui/QLabel> 00053 #include <QtGui/QLayout> 00054 00055 #include <kvbox.h> 00056 #include <QtGui/QCheckBox> 00057 00058 #include <kdebug.h> 00059 //END 00060 00061 //BEGIN KateWordCompletionModel 00062 KateWordCompletionModel::KateWordCompletionModel( QObject *parent ) 00063 : CodeCompletionModel( parent ), m_automatic(false) 00064 { 00065 setHasGroups(false); 00066 } 00067 00068 KateWordCompletionModel::~KateWordCompletionModel() 00069 { 00070 } 00071 00072 void KateWordCompletionModel::saveMatches( KTextEditor::View* view, 00073 const KTextEditor::Range& range) 00074 { 00075 m_matches = allMatches( view, range ); 00076 m_matches.sort(); 00077 } 00078 00079 QVariant KateWordCompletionModel::data(const QModelIndex& index, int role) const 00080 { 00081 if( role == InheritanceDepth ) 00082 return 10000; //Very high value, so the word-completion group and items are shown behind any other groups/items if there is multiple 00083 00084 if( !index.parent().isValid() ) { 00085 //It is the group header 00086 switch ( role ) 00087 { 00088 case Qt::DisplayRole: 00089 return i18n("Auto Word Completion"); 00090 case GroupRole: 00091 return Qt::DisplayRole; 00092 } 00093 } 00094 00095 if( index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole ) 00096 return m_matches.at( index.row() ); 00097 00098 if( index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole ) { 00099 static QIcon icon(KIcon("insert-text").pixmap(QSize(16, 16))); 00100 return icon; 00101 } 00102 00103 return QVariant(); 00104 } 00105 00106 QModelIndex KateWordCompletionModel::parent(const QModelIndex& index) const 00107 { 00108 if(index.internalId()) 00109 return createIndex(0, 0, 0); 00110 else 00111 return QModelIndex(); 00112 } 00113 00114 QModelIndex KateWordCompletionModel::index(int row, int column, const QModelIndex& parent) const 00115 { 00116 if( !parent.isValid()) { 00117 if(row == 0) 00118 return createIndex(row, column, 0); 00119 else 00120 return QModelIndex(); 00121 00122 }else if(parent.parent().isValid()) 00123 return QModelIndex(); 00124 00125 00126 if (row < 0 || row >= m_matches.count() || column < 0 || column >= ColumnCount ) 00127 return QModelIndex(); 00128 00129 return createIndex(row, column, 1); 00130 } 00131 00132 int KateWordCompletionModel::rowCount ( const QModelIndex & parent ) const 00133 { 00134 if( !parent.isValid() && !m_matches.isEmpty() ) 00135 return 1; //One root node to define the custom group 00136 else if(parent.parent().isValid()) 00137 return 0; //Completion-items have no children 00138 else 00139 return m_matches.count(); 00140 } 00141 00142 00143 bool KateWordCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) 00144 { 00145 if (!userInsertion) return false; 00146 if(insertedText.isEmpty()) 00147 return false; 00148 00149 00150 KateView *v = qobject_cast<KateView*> (view); 00151 00152 QString text = view->document()->line(position.line()).left(position.column()); 00153 uint check=v->config()->wordCompletionMinimalWordLength(); 00154 if (check<=0) return true; 00155 int start=text.length(); 00156 int end=text.length()-check; 00157 if (end<0) return false; 00158 for (int i=start-1;i>=end;i--) { 00159 QChar c=text.at(i); 00160 if (! (c.isLetter() || (c.isNumber()) || c=='_') ) return false; 00161 } 00162 00163 return true; 00164 } 00165 00166 bool KateWordCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range &range, const QString ¤tCompletion) { 00167 00168 if (m_automatic) { 00169 KateView *v = qobject_cast<KateView*> (view); 00170 if (currentCompletion.length()<v->config()->wordCompletionMinimalWordLength()) return true; 00171 } 00172 00173 return CodeCompletionModelControllerInterface4::shouldAbortCompletion(view,range,currentCompletion); 00174 } 00175 00176 00177 00178 void KateWordCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType it) 00179 { 00183 m_automatic=false; 00184 if (it==AutomaticInvocation) { 00185 m_automatic=true; 00186 KateView *v = qobject_cast<KateView*> (view); 00187 00188 if (range.columnWidth() >= v->config()->wordCompletionMinimalWordLength()) 00189 saveMatches( view, range ); 00190 else 00191 m_matches.clear(); 00192 00193 // done here... 00194 return; 00195 } 00196 00197 // normal case ;) 00198 saveMatches( view, range ); 00199 } 00200 00201 00202 // Scan throughout the entire document for possible completions, 00203 // ignoring any dublets 00204 const QStringList KateWordCompletionModel::allMatches( KTextEditor::View *view, const KTextEditor::Range &range ) const 00205 { 00206 QStringList l; 00207 00208 int i( 0 ); 00209 int pos( 0 ); 00210 KTextEditor::Document *doc = view->document(); 00211 QRegExp re( "\\b(" + doc->text( range ) + "\\w{1,})" ); 00212 QString s, m; 00213 QSet<QString> seen; 00214 00215 while( i < doc->lines() ) 00216 { 00217 s = doc->line( i ); 00218 pos = 0; 00219 while ( pos >= 0 ) 00220 { 00221 pos = re.indexIn( s, pos ); 00222 if ( pos >= 0 ) 00223 { 00224 // typing in the middle of a word 00225 if ( ! ( i == range.start().line() && pos == range.start().column() ) ) 00226 { 00227 m = re.cap( 1 ); 00228 if ( ! seen.contains( m ) ) { 00229 seen.insert( m ); 00230 l << m; 00231 } 00232 } 00233 pos += re.matchedLength(); 00234 } 00235 } 00236 i++; 00237 } 00238 return l; 00239 } 00240 00241 KTextEditor::CodeCompletionModelControllerInterface3::MatchReaction KateWordCompletionModel::matchingItem(const QModelIndex& /*matched*/) 00242 { 00243 return HideListIfAutomaticInvocation; 00244 } 00245 00246 bool KateWordCompletionModel::shouldHideItemsWithEqualNames() const 00247 { 00248 // We don't want word-completion items if the same items 00249 // are available through more sophisticated completion models 00250 return true; 00251 } 00252 00253 //END KateWordCompletionModel 00254 00255 00256 //BEGIN KateWordCompletionView 00257 struct KateWordCompletionViewPrivate 00258 { 00259 KTextEditor::MovingRange* liRange; // range containing last inserted text 00260 KTextEditor::Range dcRange; // current range to be completed by directional completion 00261 KTextEditor::Cursor dcCursor; // directional completion search cursor 00262 QRegExp re; // hrm 00263 int directionalPos; // be able to insert "" at the correct time 00264 bool isCompleting; // true when the directional completion is doing a completion 00265 }; 00266 00267 KateWordCompletionView::KateWordCompletionView( KTextEditor::View *view, KActionCollection* ac ) 00268 : QObject( view ), 00269 m_view( view ), 00270 m_dWCompletionModel( KateGlobal::self()->wordCompletionModel() ), 00271 d( new KateWordCompletionViewPrivate ) 00272 { 00273 d->isCompleting = false; 00274 d->dcRange = KTextEditor::Range::invalid(); 00275 00276 d->liRange = static_cast<KateDocument*>(m_view->document())->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand); 00277 00278 KColorScheme colors(QPalette::Active); 00279 KTextEditor::Attribute::Ptr a = KTextEditor::Attribute::Ptr( new KTextEditor::Attribute() ); 00280 a->setBackground( colors.background(KColorScheme::ActiveBackground) ); 00281 a->setForeground( colors.foreground(KColorScheme::ActiveText) ); // ### this does 0 00282 d->liRange->setAttribute( a ); 00283 00284 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(view); 00285 00286 KAction *action; 00287 00288 if (cci) 00289 { 00290 cci->registerCompletionModel( m_dWCompletionModel ); 00291 00292 action = new KAction( i18n("Shell Completion"), this ); 00293 ac->addAction( "doccomplete_sh", action ); 00294 connect( action, SIGNAL( triggered() ), this, SLOT(shellComplete()) ); 00295 } 00296 00297 00298 action = new KAction( i18n("Reuse Word Above"), this ); 00299 ac->addAction( "doccomplete_bw", action ); 00300 action->setShortcut( Qt::CTRL+Qt::Key_8 ); 00301 connect( action, SIGNAL( triggered() ), this, SLOT(completeBackwards()) ); 00302 00303 action = new KAction( i18n("Reuse Word Below"), this ); 00304 ac->addAction( "doccomplete_fw", action ); 00305 action->setShortcut( Qt::CTRL+Qt::Key_9 ); 00306 connect( action, SIGNAL( triggered() ), this, SLOT(completeForwards()) ); 00307 } 00308 00309 KateWordCompletionView::~KateWordCompletionView() 00310 { 00311 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(m_view); 00312 00313 if (cci) cci->unregisterCompletionModel(m_dWCompletionModel); 00314 00315 delete d; 00316 } 00317 00318 void KateWordCompletionView::completeBackwards() 00319 { 00320 complete( false ); 00321 } 00322 00323 void KateWordCompletionView::completeForwards() 00324 { 00325 complete(); 00326 } 00327 00328 // Pop up the editors completion list if applicable 00329 void KateWordCompletionView::popupCompletionList() 00330 { 00331 kDebug( 13040 ) << "entered ..."; 00332 KTextEditor::Range r = range(); 00333 00334 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>( m_view ); 00335 if(!cci || cci->isCompletionActive()) 00336 return; 00337 00338 m_dWCompletionModel->saveMatches( m_view, r ); 00339 00340 kDebug( 13040 ) << "after save matches ..."; 00341 00342 if ( ! m_dWCompletionModel->rowCount(QModelIndex()) ) return; 00343 00344 cci->startCompletion( r, m_dWCompletionModel ); 00345 } 00346 00347 // Contributed by <brain@hdsnet.hu> 00348 void KateWordCompletionView::shellComplete() 00349 { 00350 KTextEditor::Range r = range(); 00351 00352 QStringList matches = m_dWCompletionModel->allMatches( m_view, r ); 00353 00354 if (matches.size() == 0) 00355 return; 00356 00357 QString partial = findLongestUnique( matches, r.columnWidth() ); 00358 00359 if ( ! partial.length() ) 00360 popupCompletionList(); 00361 00362 else 00363 { 00364 m_view->document()->insertText( r.end(), partial.mid( r.columnWidth() ) ); 00365 d->liRange->setView(m_view); 00366 d->liRange->setRange( KTextEditor::Range( r.end(), partial.length() - r.columnWidth() ) ); 00367 connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) ); 00368 } 00369 } 00370 00371 // Do one completion, searching in the desired direction, 00372 // if possible 00373 void KateWordCompletionView::complete( bool fw ) 00374 { 00375 KTextEditor::Range r = range(); 00376 00377 int inc = fw ? 1 : -1; 00378 KTextEditor::Document *doc = m_view->document(); 00379 00380 if ( d->dcRange.isValid() ) 00381 { 00382 //kDebug( 13040 )<<"CONTINUE "<<d->dcRange; 00383 // this is a repeted activation 00384 00385 // if we are back to where we started, reset. 00386 if ( ( fw && d->directionalPos == -1 ) || 00387 ( !fw && d->directionalPos == 1 ) ) 00388 { 00389 const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); 00390 if ( spansColumns > 0 ) 00391 doc->removeText( *d->liRange ); 00392 00393 d->liRange->setRange( KTextEditor::Range::invalid() ); 00394 d->dcCursor = r.end(); 00395 d->directionalPos = 0; 00396 00397 return; 00398 } 00399 00400 if ( fw ) { 00401 const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); 00402 d->dcCursor.setColumn( d->dcCursor.column() + spansColumns ); 00403 } 00404 00405 d->directionalPos += inc; 00406 } 00407 else // new completion, reset all 00408 { 00409 //kDebug( 13040 )<<"RESET FOR NEW"; 00410 d->dcRange = r; 00411 d->liRange->setRange( KTextEditor::Range::invalid() ); 00412 d->dcCursor = r.start(); 00413 d->directionalPos = inc; 00414 00415 d->liRange->setView( m_view ); 00416 00417 connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) ); 00418 00419 } 00420 00421 d->re.setPattern( "\\b" + doc->text( d->dcRange ) + "(\\w+)" ); 00422 int pos ( 0 ); 00423 QString ln = doc->line( d->dcCursor.line() ); 00424 00425 while ( true ) 00426 { 00427 //kDebug( 13040 )<<"SEARCHING FOR "<<d->re.pattern()<<" "<<ln<<" at "<<d->dcCursor; 00428 pos = fw ? 00429 d->re.indexIn( ln, d->dcCursor.column() ) : 00430 d->re.lastIndexIn( ln, d->dcCursor.column() ); 00431 00432 if ( pos > -1 ) // we matched a word 00433 { 00434 //kDebug( 13040 )<<"USABLE MATCH"; 00435 QString m = d->re.cap( 1 ); 00436 if ( m != doc->text( *d->liRange ) && (d->dcCursor.line() != d->dcRange.start().line() || pos != d->dcRange.start().column() ) ) 00437 { 00438 // we got good a match! replace text and return. 00439 d->isCompleting = true; 00440 KTextEditor::Range replaceRange(d->liRange->toRange()); 00441 if (!replaceRange.isValid()) { 00442 replaceRange.setRange(r.end(), r.end()); 00443 } 00444 doc->replaceText( replaceRange, m ); 00445 d->liRange->setRange( KTextEditor::Range( d->dcRange.end(), m.length() ) ); 00446 00447 d->dcCursor.setColumn( pos ); // for next try 00448 00449 d->isCompleting = false; 00450 return; 00451 } 00452 00453 // equal to last one, continue 00454 else 00455 { 00456 //kDebug( 13040 )<<"SKIPPING, EQUAL MATCH"; 00457 d->dcCursor.setColumn( pos ); // for next try 00458 00459 if ( fw ) 00460 d->dcCursor.setColumn( pos + m.length() ); 00461 00462 else 00463 { 00464 if ( pos == 0 ) 00465 { 00466 if ( d->dcCursor.line() > 0 ) 00467 { 00468 int l = d->dcCursor.line() + inc; 00469 ln = doc->line( l ); 00470 d->dcCursor.setPosition( l, ln.length() ); 00471 } 00472 else 00473 { 00474 KNotification::beep(); 00475 return; 00476 } 00477 } 00478 00479 else 00480 d->dcCursor.setColumn( d->dcCursor.column()-1 ); 00481 } 00482 } 00483 } 00484 00485 else // no match 00486 { 00487 //kDebug( 13040 )<<"NO MATCH"; 00488 if ( (! fw && d->dcCursor.line() == 0 ) || ( fw && d->dcCursor.line() >= doc->lines() ) ) 00489 { 00490 KNotification::beep(); 00491 return; 00492 } 00493 00494 int l = d->dcCursor.line() + inc; 00495 ln = doc->line( l ); 00496 d->dcCursor.setPosition( l, fw ? 0 : ln.length() ); 00497 } 00498 } // while true 00499 } 00500 00501 void KateWordCompletionView::slotCursorMoved() 00502 { 00503 if ( d->isCompleting) return; 00504 00505 d->dcRange = KTextEditor::Range::invalid(); 00506 00507 disconnect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) ); 00508 00509 d->liRange->setView(0); 00510 d->liRange->setRange(KTextEditor::Range::invalid()); 00511 } 00512 00513 // Contributed by <brain@hdsnet.hu> FIXME 00514 QString KateWordCompletionView::findLongestUnique( const QStringList &matches, int lead ) const 00515 { 00516 QString partial = matches.first(); 00517 00518 QStringListIterator it( matches ); 00519 QString current; 00520 while ( it.hasNext() ) 00521 { 00522 current = it.next(); 00523 if ( !current.startsWith( partial ) ) 00524 { 00525 while( partial.length() > lead ) 00526 { 00527 partial.remove( partial.length() - 1, 1 ); 00528 if ( current.startsWith( partial ) ) 00529 break; 00530 } 00531 00532 if ( partial.length() == lead ) 00533 return QString(); 00534 } 00535 } 00536 00537 return partial; 00538 } 00539 00540 // Return the string to complete (the letters behind the cursor) 00541 const QString KateWordCompletionView::word() const 00542 { 00543 return m_view->document()->text( range() ); 00544 } 00545 00546 // Return the range containing the word behind the cursor 00547 const KTextEditor::Range KateWordCompletionView::range() const 00548 { 00549 KTextEditor::Cursor end = m_view->cursorPosition(); 00550 00551 int line = end.line(); 00552 int col = end.column(); 00553 00554 KTextEditor::Document *doc = m_view->document(); 00555 while ( col > 0 ) 00556 { 00557 QChar c = ( doc->character( KTextEditor::Cursor( line, col-1 ) ) ); 00558 if ( c.isLetterOrNumber() || c.isMark() || c == '_' ) 00559 { 00560 col--; 00561 continue; 00562 } 00563 00564 break; 00565 } 00566 00567 return KTextEditor::Range( KTextEditor::Cursor( line, col ), end ); 00568 } 00569 //END 00570 00571 #include "katewordcompletion.moc" 00572 // kate: space-indent on; indent-width 2; replace-tabs on; mixed-indent off;
KDE 4.6 API Reference