scide: DocManager - report warnings via the status bar
[supercollider.git] / editors / sc-ide / widgets / code_editor / autocompleter.cpp
blob7637e6fae08cad738c6ee9c6fabadc4983ee6900
1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #define QT_NO_DEBUG_OUTPUT
23 #include "autocompleter.hpp"
24 #include "editor.hpp"
25 #include "tokens.hpp"
26 #include "../util/popup_widget.hpp"
27 #include "../../core/sc_introspection.hpp"
28 #include "../../core/sc_process.hpp"
29 #include "../../core/main.hpp"
31 #include "yaml-cpp/node.h"
32 #include "yaml-cpp/parser.h"
34 #include <QDebug>
35 #include <QLabel>
36 #include <QListView>
37 #include <QStandardItemModel>
38 #include <QStandardItem>
39 #include <QHBoxLayout>
40 #include <QApplication>
42 #ifdef Q_WS_X11
43 # include <QGtkStyle>
44 #endif
46 namespace ScIDE {
48 static bool tokenMaybeName( Token::Type type )
50 return (type == Token::Name || type == Token::Keyword || type == Token::Builtin);
53 static QString incrementedString( const QString & other )
55 if(other.isEmpty())
56 return QString();
58 QString str = other;
59 int pos = str.length()-1;
60 str[pos] = QChar( str[pos].unicode() + 1 );
61 return str;
64 class CompletionMenu : public PopUpWidget
66 public:
67 enum DataRole {
68 CompletionRole = Qt::UserRole,
69 MethodRole
72 CompletionMenu( QWidget * parent = 0 ):
73 PopUpWidget(parent),
74 mCompletionRole( Qt::DisplayRole )
76 mModel = new QStandardItemModel(this);
77 mFilterModel = new QSortFilterProxyModel(this);
78 mFilterModel->setSourceModel(mModel);
80 mListView = new QListView();
81 mListView->setModel(mFilterModel);
82 mListView->setFrameShape(QFrame::NoFrame);
84 QHBoxLayout *layout = new QHBoxLayout(this);
85 layout->addWidget(mListView);
86 layout->setContentsMargins(1,1,1,1);
88 connect(mListView, SIGNAL(clicked(QModelIndex)), this, SLOT(accept()));
90 mListView->setFocus(Qt::OtherFocusReason);
92 resize(200, 200);
94 parent->installEventFilter(this);
97 void addItem( QStandardItem * item )
99 mModel->appendRow( item );
102 void setCompletionRole( int role )
104 mFilterModel->setFilterRole(role);
105 mFilterModel->setSortRole(role);
106 mCompletionRole = role;
109 QString currentText()
111 QStandardItem *item =
112 mModel->itemFromIndex (
113 mFilterModel->mapToSource (
114 mListView->currentIndex()));
115 if (item)
116 return item->data(mCompletionRole).toString();
118 return QString();
121 const ScLanguage::Method * currentMethod()
123 QStandardItem *item =
124 mModel->itemFromIndex (
125 mFilterModel->mapToSource (
126 mListView->currentIndex()));
128 return item ? item->data(MethodRole).value<const ScLanguage::Method*>() : 0;
131 QString exec( const QPoint & pos )
133 QString result;
134 QPointer<CompletionMenu> self = this;
135 if (PopUpWidget::exec(pos)) {
136 if (!self.isNull())
137 result = currentText();
139 return result;
142 QSortFilterProxyModel *model() { return mFilterModel; }
144 QListView *view() { return mListView; }
146 protected:
147 virtual bool eventFilter( QObject * obj, QEvent * ev )
149 if (isVisible() && obj == parentWidget() && ev->type() == QEvent::KeyPress)
151 QKeyEvent *kev = static_cast<QKeyEvent*>(ev);
152 switch(kev->key())
154 case Qt::Key_Up:
155 case Qt::Key_Down:
156 case Qt::Key_PageUp:
157 case Qt::Key_PageDown:
158 QApplication::sendEvent( mListView, ev );
159 return true;
160 case Qt::Key_Return:
161 case Qt::Key_Enter:
162 accept();
163 return true;
167 return PopUpWidget::eventFilter(obj, ev);
170 private:
171 QListView *mListView;
172 QStandardItemModel *mModel;
173 QSortFilterProxyModel *mFilterModel;
174 int mCompletionRole;
177 class MethodCallWidget : public QWidget
179 public:
180 MethodCallWidget( QWidget * parent = 0 ):
181 QWidget( parent, Qt::ToolTip )
183 mLabel = new QLabel();
184 mLabel->setTextFormat( Qt::RichText );
186 #ifdef Q_WS_X11
187 if (qobject_cast<QGtkStyle*>(style()) != 0) {
188 QPalette p;
189 p.setColor( QPalette::Window, QColor(255, 255, 220) );
190 p.setColor( QPalette::WindowText, Qt::black );
191 setPalette(p);
193 else
194 #endif
196 QPalette p( palette() );
197 p.setColor( QPalette::Window, p.color(QPalette::ToolTipBase) );
198 setPalette(p);
199 mLabel->setForegroundRole(QPalette::ToolTipText);
202 QHBoxLayout *box = new QHBoxLayout;
203 box->setContentsMargins(5,2,5,2);
204 box->addWidget(mLabel);
205 setLayout(box);
208 void showMethod( const ScLanguage::Method * method, int argNum )
210 QString text;
211 int argc = method->arguments.count();
212 for (int i = 0; i < argc; ++i)
214 const ScLanguage::Argument & arg = method->arguments[i];
216 if (i == argNum) {
217 text += QString(
218 "<span style=\""
219 //"text-decoration: underline;"
220 "font-weight: bold;"
221 "\">");
224 text += arg.name;
226 QString val = arg.defaultValue;
227 if (!val.isEmpty())
228 text += " = " + val;
230 if (i == argNum)
231 text += "</span>";
233 if (i != argc - 1)
234 text += ", &nbsp;&nbsp;";
236 mLabel->setText(text);
239 private:
240 QLabel *mLabel;
243 AutoCompleter::AutoCompleter( CodeEditor *editor ):
244 QObject(editor),
245 mEditor(editor)
247 mCompletion.on = false;
248 mEditor->installEventFilter(this);
250 connect(editor, SIGNAL(cursorPositionChanged()),
251 this, SLOT(onCursorChanged()));
254 void AutoCompleter::documentChanged( QTextDocument * doc )
256 connect(doc, SIGNAL(contentsChange(int,int,int)),
257 this, SLOT(onContentsChange(int,int,int)));
260 inline QTextDocument *AutoCompleter::document()
262 return static_cast<QPlainTextEdit*>(mEditor)->document();
265 void AutoCompleter::keyPress( QKeyEvent *e )
267 int key = e->key();
268 switch (e->key())
270 case Qt::Key_ParenLeft:
271 case Qt::Key_Comma:
272 triggerMethodCallAid(false);
273 break;
274 case Qt::Key_Backspace:
275 case Qt::Key_Delete:
276 return;
277 default:
278 qDebug(">>> key");
279 // Only trigger completion if event produces at least 1 printable character:
280 if (!mCompletion.on && !e->text().isEmpty() && e->text()[0].isPrint() )
281 triggerCompletion();
285 bool AutoCompleter::eventFilter( QObject *object, QEvent *event )
287 if (object != mEditor)
288 return false;
290 switch(event->type()) {
291 case QEvent::FocusOut:
292 if (mCompletion.menu)
293 mCompletion.menu->reject();
294 if (mMethodCall.menu)
295 mMethodCall.menu->reject();
296 if (mMethodCall.widget)
297 mMethodCall.widget->hide();
298 break;
299 case QEvent::ShortcutOverride: {
300 QKeyEvent * kevent = static_cast<QKeyEvent*>(event);
301 if (kevent->key() == Qt::Key_Escape) {
302 if (mCompletion.menu && mCompletion.menu->isVisible())
303 mCompletion.menu->reject();
304 else if (mMethodCall.menu && mMethodCall.menu->isVisible())
305 mMethodCall.menu->reject();
306 else if (mMethodCall.widget && mMethodCall.widget->isVisible()) {
307 // disable method call aid for current call:
308 Q_ASSERT(!mMethodCall.stack.isEmpty());
309 mMethodCall.stack.top().method = 0;
310 hideMethodCall();
312 else break;
313 return true;
315 break;
317 default:;
320 return QObject::eventFilter(object, event);
323 void AutoCompleter::onContentsChange( int pos, int removed, int added )
325 qDebug(">>> contentsChange");
327 while (!mMethodCall.stack.isEmpty())
329 MethodCall & call = mMethodCall.stack.top();
330 if (pos > call.position)
331 break;
332 else {
333 qDebug("Method call: change before method call. popping.");
334 mMethodCall.stack.pop();
338 if (mCompletion.on)
340 if(pos < mCompletion.contextPos)
342 quitCompletion("context changed");
344 else if(pos <= mCompletion.pos + mCompletion.len)
346 QTextBlock block( document()->findBlock(mCompletion.pos) );
347 TokenIterator it( block, mCompletion.pos - block.position() );
348 Token::Type type = it.type();
349 if (type == Token::Class || tokenMaybeName(type)) {
350 mCompletion.len = it->length;
351 mCompletion.text = tokenText(it);
353 else {
354 mCompletion.len = 0;
355 mCompletion.text.clear();
357 if (!mCompletion.menu.isNull())
358 updateCompletionMenu(false);
363 void AutoCompleter::onCursorChanged()
365 qDebug(">>> cursorChanged");
366 int cursorPos = mEditor->textCursor().position();
368 // completion
369 if (mCompletion.on) {
370 if (cursorPos < mCompletion.pos ||
371 cursorPos > mCompletion.pos + mCompletion.len)
373 quitCompletion("out of bounds");
377 if (!mMethodCall.menu.isNull()) {
378 qDebug("Method call: quitting menu");
379 delete mMethodCall.menu;
382 updateMethodCall(cursorPos);
385 void AutoCompleter::triggerCompletion(bool forceShow)
387 if (mCompletion.on) {
388 qDebug("AutoCompleter::triggerCompletion(): completion already started.");
389 updateCompletionMenu(forceShow);
390 return;
393 QTextCursor cursor( mEditor->textCursor() );
394 const int cursorPos = cursor.positionInBlock();
395 QTextBlock block( cursor.block() );
396 TokenIterator it( block, cursorPos - 1 );
398 if (!it.isValid())
399 return;
401 const Token & triggeringToken = *it;
403 if (triggeringToken.type == Token::Class)
405 if (triggeringToken.length < 3)
406 return;
407 mCompletion.type = ClassCompletion;
408 mCompletion.pos = it.position();
409 mCompletion.len = it->length;
410 mCompletion.text = tokenText(it);
411 mCompletion.contextPos = mCompletion.pos + 3;
412 mCompletion.base = mCompletion.text;
413 mCompletion.base.truncate(3);
415 else {
416 TokenIterator objectIt, dotIt, nameIt;
418 Token::Type objectTokenType = Token::Unknown;
420 if (tokenMaybeName(it.type())) {
421 nameIt = it;
422 --it;
425 if (it.isValid() && it.character() == '.') {
426 dotIt = it;
427 --it;
429 else
430 // don't trigger on method names without preceding dot (for now)
431 return;
433 if (dotIt.isValid()) {
434 objectTokenType = it.type();
435 switch (objectTokenType) {
436 case Token::Class:
437 case Token::Char:
438 case Token::String:
439 case Token::Builtin:
440 case Token::Symbol:
441 case Token::Float:
442 case Token::RadixFloat:
443 case Token::HexInt:
444 objectIt = it;
445 break;
447 default:;
451 if (!objectIt.isValid() && (!nameIt.isValid() || nameIt->length < 3))
452 return;
454 if (nameIt.isValid()) {
455 mCompletion.pos = nameIt.position();
456 mCompletion.len = nameIt->length;
457 mCompletion.text = tokenText(nameIt);
458 } else {
459 mCompletion.pos = dotIt.position() + 1;
460 mCompletion.len = 0;
461 mCompletion.text.clear();
464 if (objectIt.isValid()) {
465 mCompletion.contextPos = mCompletion.pos;
466 mCompletion.base = tokenText(objectIt);
467 mCompletion.tokenType = objectTokenType;
468 mCompletion.type = ClassMethodCompletion;
470 else {
471 mCompletion.contextPos = mCompletion.pos + 3;
472 mCompletion.base = tokenText(nameIt);
473 mCompletion.type = MethodCompletion;
477 mCompletion.on = true;
479 qDebug() << QString("Completion: ON <%1>").arg(mCompletion.base);
481 showCompletionMenu(forceShow);
483 if (mCompletion.menu.isNull())
484 mCompletion.on = false;
487 void AutoCompleter::quitCompletion( const QString & reason )
489 Q_ASSERT(mCompletion.on);
491 qDebug() << QString("Completion: OFF (%1)").arg(reason);
493 if (mCompletion.menu) {
494 mCompletion.menu->hide();
495 mCompletion.menu->deleteLater();
496 mCompletion.menu = 0;
499 mCompletion.on = false;
502 void AutoCompleter::showCompletionMenu(bool forceShow)
504 qDebug(">>> showCompletionMenu");
506 using namespace ScLanguage;
507 using ScLanguage::Method;
509 Q_ASSERT(mCompletion.on);
510 Q_ASSERT(mCompletion.menu.isNull());
512 QPointer<CompletionMenu> menu;
514 switch (mCompletion.type) {
515 case ClassCompletion:
516 menu = menuForClassCompletion(mCompletion, mEditor);
517 break;
519 case ClassMethodCompletion:
520 menu = menuForClassMethodCompletion(mCompletion, mEditor);
521 break;
523 case MethodCompletion:
524 menu = menuForMethodCompletion(mCompletion, mEditor);
525 break;
529 if (menu == NULL) return;
531 mCompletion.menu = menu;
533 connect(menu, SIGNAL(finished(int)), this, SLOT(onCompletionMenuFinished(int)));
535 QTextCursor cursor(document());
536 cursor.setPosition(mCompletion.pos);
537 QPoint pos = mEditor->viewport()->mapToGlobal( mEditor->cursorRect(cursor).bottomLeft() )
538 + QPoint(0,5);
540 menu->popup(pos);
542 updateCompletionMenu(forceShow);
545 CompletionMenu * AutoCompleter::menuForClassCompletion(CompletionDescription const & completion,
546 CodeEditor * editor)
548 using namespace ScLanguage;
549 const Introspection & introspection = Main::scProcess()->introspection();
551 const ClassMap & classes = introspection.classMap();
553 QString min = completion.base;
554 QString max = incrementedString(min);
556 ClassMap::const_iterator matchStart, matchEnd;
557 matchStart = classes.lower_bound(min);
558 matchEnd = classes.lower_bound(max);
559 if (matchStart == matchEnd) {
560 qDebug() << "Completion: no class matches:" << completion.base;
561 return NULL;
564 CompletionMenu * menu = new CompletionMenu(editor);
566 for (ClassMap::const_iterator it = matchStart; it != matchEnd; ++it) {
567 Class *klass = it->second.data();
568 menu->addItem( new QStandardItem(klass->name) );
571 return menu;
574 CompletionMenu * AutoCompleter::menuForClassMethodCompletion(CompletionDescription const & completion,
575 CodeEditor * editor)
577 using namespace ScLanguage;
578 const Introspection & introspection = Main::scProcess()->introspection();
580 const Class *klass = NULL;
582 if (completion.tokenType == Token::Class) {
583 const ClassMap & classes = introspection.classMap();
584 ClassMap::const_iterator it = classes.find(completion.base);
585 if (it != classes.end())
586 klass = it->second->metaClass;
588 else {
589 klass = classForCompletionDescription(completion);
592 if (klass == NULL) {
593 qDebug() << "Autocompletion not implemented for" << completion.base;
594 return NULL;
597 QMap<QString, const Method*> relevantMethods;
598 do {
599 foreach (const Method * method, klass->methods)
601 QString methodName = method->name.get();
603 // Operators are also methods, but are not valid in
604 // a method call syntax, so filter them out.
605 Q_ASSERT(!methodName.isEmpty());
606 if (!methodName[0].isLetter())
607 continue;
609 if (relevantMethods.value(methodName) != 0)
610 continue;
612 relevantMethods.insert(methodName, method);
614 klass = klass->superClass;
615 } while (klass);
617 CompletionMenu * menu = new CompletionMenu(editor);
618 menu->setCompletionRole(CompletionMenu::CompletionRole);
620 foreach(const Method *method, relevantMethods) {
621 QString methodName = method->name.get();
622 QString detail(" [ %1 ]");
624 QStandardItem *item = new QStandardItem();
625 item->setText( methodName + detail.arg(method->ownerClass->name) );
626 item->setData( QVariant::fromValue(method), CompletionMenu::MethodRole );
627 item->setData( methodName, CompletionMenu::CompletionRole );
628 menu->addItem(item);
631 return menu;
634 CompletionMenu * AutoCompleter::menuForMethodCompletion(CompletionDescription const & completion,
635 CodeEditor * editor)
637 using namespace ScLanguage;
638 const Introspection & introspection = Main::scProcess()->introspection();
640 const MethodMap & methods = introspection.methodMap();
642 QString min = completion.base;
643 QString max = incrementedString(min);
645 MethodMap::const_iterator matchStart, matchEnd;
646 matchStart = methods.lower_bound(min);
647 matchEnd = methods.lower_bound(max);
648 if (matchStart == matchEnd) {
649 qDebug() << "Completion: no method matches:" << completion.base;
650 return NULL;
653 CompletionMenu *menu = new CompletionMenu(editor);
654 menu->setCompletionRole(CompletionMenu::CompletionRole);
656 for (MethodMap::const_iterator it = matchStart; it != matchEnd; ) {
657 const Method *method = it->second.data();
659 std::pair<MethodMap::const_iterator, MethodMap::const_iterator> range
660 = methods.equal_range(it->first);
662 int count = std::distance(range.first, range.second);
664 QStandardItem *item = new QStandardItem();
666 QString methodName = method->name.get();
667 QString detail(" [ %1 ]");
668 if (count == 1) {
669 item->setText( methodName + detail.arg(method->ownerClass->name) );
670 item->setData( QVariant::fromValue(method), CompletionMenu::MethodRole );
671 } else
672 item->setText(methodName + detail.arg(count));
674 item->setData(methodName, CompletionMenu::CompletionRole);
676 menu->addItem(item);
678 it = range.second;
680 return menu;
683 const ScLanguage::Class * AutoCompleter::classForCompletionDescription(CompletionDescription const & completion)
685 using namespace ScLanguage;
686 const Introspection & introspection = Main::scProcess()->introspection();
688 switch (completion.tokenType) {
689 case Token::Float:
690 case Token::RadixFloat:
691 case Token::HexInt:
692 if (completion.base.contains(".")) // else it is an int
693 return introspection.findClass("Float");
694 else if (!completion.text.isEmpty())
695 return introspection.findClass("Integer");
696 else
697 return NULL;
699 case Token::Char:
700 return introspection.findClass("Char");
702 case Token::String:
703 return introspection.findClass("String");
705 case Token::Symbol:
706 return introspection.findClass("Symbol");
708 default:
712 QString const & objectString = completion.base;
714 if (objectString == QString("true"))
715 return introspection.findClass("True");
717 if (objectString == QString("false"))
718 return introspection.findClass("False");
720 if (objectString == QString("nil"))
721 return introspection.findClass("Nil");
723 if (objectString == QString("thisProcess"))
724 return introspection.findClass("Main");
726 if (objectString == QString("thisFunction"))
727 return introspection.findClass("Function");
729 if (objectString == QString("thisMethod"))
730 return introspection.findClass("Method");
732 if (objectString == QString("thisFunctionDef"))
733 return introspection.findClass("FunctionDef");
735 if (objectString == QString("thisThread"))
736 return introspection.findClass("Thread");
738 if (objectString == QString("currentEnvironment"))
739 return introspection.findClass("Environment");
741 if (objectString == QString("topEnvironment"))
742 return introspection.findClass("Environment");
744 if (objectString == QString("inf"))
745 return introspection.findClass("Float");
747 return NULL;
750 void AutoCompleter::updateCompletionMenu(bool forceShow)
752 Q_ASSERT(mCompletion.on && !mCompletion.menu.isNull());
754 CompletionMenu *menu = mCompletion.menu;
756 if (!mCompletion.text.isEmpty()) {
757 QString pattern = mCompletion.text;
758 pattern.prepend("^");
759 menu->model()->setFilterRegExp(pattern);
760 } else
761 menu->model()->setFilterRegExp(QString());
763 if (menu->model()->hasChildren()) {
764 menu->model()->sort(0);
765 menu->view()->setCurrentIndex( menu->model()->index(0,0) );
766 if (forceShow || menu->currentText() != mCompletion.text)
767 menu->show();
768 else
769 menu->hide();
770 } else
771 menu->hide();
774 void AutoCompleter::onCompletionMenuFinished( int result )
776 qDebug("Completion: menu finished");
778 if (!mCompletion.on)
779 return;
781 if (result) {
782 QString text = mCompletion.menu->currentText();
784 if (!text.isEmpty()) {
785 quitCompletion("done");
787 QTextCursor cursor( mEditor->textCursor() );
788 cursor.setPosition( mCompletion.pos );
789 cursor.setPosition( mCompletion.pos + mCompletion.len, QTextCursor::KeepAnchor );
790 cursor.insertText(text);
792 return;
796 // Do not cancel completion whenever menu hidden.
797 // It could be hidden because of current filter yielding 0 results.
799 //quitCompletion("cancelled");
802 void AutoCompleter::triggerMethodCallAid( bool force )
804 // go find the bracket that I'm currently in,
805 // and count relevant commas along the way
807 QTextDocument *doc = document();
808 QTextCursor cursor( mEditor->textCursor() );
810 int pos = cursor.position();
812 QTextBlock block( doc->findBlock(pos) );
813 if (!block.isValid())
814 return;
815 pos -= block.position();
817 TokenIterator it( TokenIterator::leftOf( block, pos ) );
819 int level = 1;
820 int argPos = 0;
822 while (it.isValid())
824 char chr = it->character;
825 Token::Type type = it->type;
826 if (chr == ',') {
827 if (level == 1)
828 ++argPos;
830 else if (type == Token::ClosingBracket)
831 ++level;
832 else if (type == Token::OpeningBracket)
834 --level;
835 if (level == 0) {
836 if (chr == '(')
837 break;
838 else
839 return;
842 --it;
845 if (!it.isValid())
846 return;
848 int bracketPos;
849 pos = bracketPos = it.position();
851 QString className, methodName;
853 --it;
854 if (it.isValid())
856 int type = it->type;
857 if (type == Token::Class) {
858 className = tokenText(it);
859 methodName = "new";
861 else if (type == Token::Name) {
862 methodName = tokenText(it);
863 --it;
864 if (it.isValid() && it->character == '.') {
865 --it;
866 if (it.isValid() && it->type == Token::Class)
867 className = tokenText(it);
872 if (methodName.isEmpty())
873 return;
875 qDebug("Method call: found call: %s.%s(%i)",
876 className.toStdString().c_str(),
877 methodName.toStdString().c_str(),
878 argPos);
880 if ( !mMethodCall.stack.isEmpty() && mMethodCall.stack.last().position == bracketPos )
882 qDebug("Method call: call already on stack");
883 // method call popup should have been updated by updateMethodCall();
885 if (force) {
886 qDebug("Method call: forced re-trigger, popping current call.");
887 mMethodCall.stack.pop();
888 hideMethodCall();
890 else
891 return;
894 qDebug("Method call: new call");
895 MethodCall call;
896 call.position = bracketPos;
897 pushMethodCall(call);
899 using namespace ScLanguage;
900 using std::pair;
902 const Introspection & introspection = Main::scProcess()->introspection();
904 const Method *method = 0;
906 if (!className.isEmpty())
908 const ClassMap & classes = introspection.classMap();
909 ClassMap::const_iterator it = classes.find(className);
910 if (it == classes.end()) {
911 qDebug() << "MethodCall: class not found:" << className;
912 return;
915 Class *metaClass = it->second->metaClass;
916 do {
917 foreach (const Method * m, metaClass->methods)
919 if (m->name == methodName) {
920 method = m;
921 break;
924 if (method) break;
925 metaClass = metaClass->superClass;
926 } while (metaClass);
928 else {
929 const MethodMap & methods = introspection.methodMap();
931 pair<MethodMap::const_iterator, MethodMap::const_iterator> match =
932 methods.equal_range(methodName);
934 if (match.first == match.second) {
935 qDebug() << "MethodCall: no method matches:" << methodName;
936 return;
937 } else if (std::distance(match.first, match.second) == 1)
938 method = match.first->second.data();
939 else {
940 Q_ASSERT(mMethodCall.menu.isNull());
941 QPointer<CompletionMenu> menu = new CompletionMenu(mEditor);
942 mMethodCall.menu = menu;
944 for (MethodMap::const_iterator it = match.first; it != match.second; ++it)
946 const Method *method = it->second.data();
947 QStandardItem *item = new QStandardItem();
948 item->setText(method->name + " (" + method->ownerClass->name + ')');
949 item->setData( QVariant::fromValue(method), CompletionMenu::MethodRole );
950 menu->addItem(item);
953 QTextCursor cursor(document());
954 cursor.setPosition(bracketPos);
955 QPoint pos =
956 mEditor->viewport()->mapToGlobal( mEditor->cursorRect(cursor).bottomLeft() )
957 + QPoint(0,5);
959 if ( ! static_cast<PopUpWidget*>(menu)->exec(pos) ) {
960 delete menu;
961 return;
964 method = menu->currentMethod();
965 delete menu;
969 if (method) {
970 Q_ASSERT(!mMethodCall.stack.isEmpty());
971 mMethodCall.stack.top().method = method;
972 updateMethodCall( mEditor->textCursor().position() );
976 void AutoCompleter::updateMethodCall( int cursorPos )
978 int i = mMethodCall.stack.count();
979 while (i--)
981 MethodCall & call = mMethodCall.stack[i];
982 if (call.position >= cursorPos) {
983 qDebug("Method call: call right of cursor. popping.");
984 mMethodCall.stack.pop();
985 continue;
988 QTextBlock block( document()->findBlock( call.position ) );
989 TokenIterator token = TokenIterator::rightOf(block, call.position - block.position());
990 if (!token.isValid()) {
991 qWarning("Method call: call stack out of sync!");
992 mMethodCall.stack.clear();
993 break;
996 ++token;
997 int arg = 0;
998 int level = 1;
999 TokenIterator argNameToken;
1000 while( level > 0 && token.isValid() && token.position() < cursorPos )
1002 char chr = token.character();
1003 Token::Type type = token->type;
1004 if (level == 1) {
1005 if (type == Token::SymbolArg) {
1006 argNameToken = token;
1007 arg = -1;
1009 else if (chr == ',') {
1010 argNameToken = TokenIterator();
1011 if (arg != -1)
1012 ++arg;
1016 if (type == Token::OpeningBracket)
1017 ++level;
1018 else if (type == Token::ClosingBracket)
1019 --level;
1021 ++token;
1024 if (level <= 0) {
1025 Q_ASSERT(i == mMethodCall.stack.count() - 1);
1026 qDebug("Method call: call left of cursor. popping.");
1027 mMethodCall.stack.pop();
1028 continue;
1031 if (!call.method || !call.method->arguments.count()) {
1032 qDebug("Method call: no info to show. skipping.");
1033 continue;
1036 if (argNameToken.isValid()) {
1037 arg = -1;
1038 QString argName = tokenText(argNameToken);
1039 argName.chop(1);
1040 for (int idx = 0; idx < call.method->arguments.count(); ++idx) {
1041 if (call.method->arguments[idx].name == argName) {
1042 arg = idx;
1043 break;
1047 qDebug("Method call: found current call: %s(%i)",
1048 call.method->name.get().toStdString().c_str(), arg);
1049 showMethodCall(call, arg);
1050 return;
1053 hideMethodCall();
1056 void AutoCompleter::pushMethodCall( const MethodCall & call )
1058 qDebug("Method Call: pushing on stack.");
1059 Q_ASSERT( mMethodCall.stack.isEmpty()
1060 || mMethodCall.stack.last().position < call.position );
1062 mMethodCall.stack.push(call);
1065 void AutoCompleter::showMethodCall( const MethodCall & call, int arg )
1067 QTextCursor cursor(document());
1068 cursor.setPosition(call.position);
1069 QPoint pos =
1070 mEditor->viewport()->mapToGlobal( mEditor->cursorRect(cursor).topLeft() );
1071 pos += QPoint(0, -20);
1073 if (mMethodCall.widget.isNull())
1074 mMethodCall.widget = new MethodCallWidget(mEditor);
1076 MethodCallWidget *w = mMethodCall.widget;
1078 w->showMethod( call.method, arg );
1079 w->resize(w->sizeHint());
1080 w->move(pos);
1081 w->show();
1084 void AutoCompleter::hideMethodCall()
1086 delete mMethodCall.widget;
1089 QString AutoCompleter::tokenText( TokenIterator & it )
1091 if (!it.isValid())
1092 return QString();
1094 int pos = it.position();
1095 QTextCursor cursor(document());
1096 cursor.setPosition(pos);
1097 cursor.setPosition(pos + it->length, QTextCursor::KeepAnchor);
1098 return cursor.selectedText();
1101 } // namespace ScIDE
1103 #undef QT_NO_DEBUG_OUTPUT