1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "cppeditor.h"
35 #include "cppeditorconstants.h"
36 #include "cppplugin.h"
37 #include "cpphighlighter.h"
38 #include "cppchecksymbols.h"
39 #include "cppquickfix.h"
40 #include "cpplocalsymbols.h"
41 #include "cppquickfixcollector.h"
42 #include "cppqtstyleindenter.h"
43 #include "cppautocompleter.h"
51 #include <CoreTypes.h>
53 #include <ASTVisitor.h>
54 #include <SymbolVisitor.h>
55 #include <TranslationUnit.h>
56 #include <cplusplus/ASTPath.h>
57 #include <cplusplus/ModelManagerInterface.h>
58 #include <cplusplus/ExpressionUnderCursor.h>
59 #include <cplusplus/TypeOfExpression.h>
60 #include <cplusplus/Overview.h>
61 #include <cplusplus/OverviewModel.h>
62 #include <cplusplus/SimpleLexer.h>
63 #include <cplusplus/MatchingText.h>
64 #include <cplusplus/BackwardsScanner.h>
65 #include <cplusplus/FastPreprocessor.h>
67 #include <cpptools/cpptoolsplugin.h>
68 #include <cpptools/cpptoolsconstants.h>
69 #include <cpptools/cppcodeformatter.h>
71 #include <coreplugin/icore.h>
72 #include <coreplugin/actionmanager/actionmanager.h>
73 #include <coreplugin/actionmanager/actioncontainer.h>
74 #include <coreplugin/actionmanager/command.h>
75 #include <coreplugin/uniqueidmanager.h>
76 #include <coreplugin/editormanager/ieditor.h>
77 #include <coreplugin/editormanager/editormanager.h>
78 #include <coreplugin/mimedatabase.h>
79 #include <utils/uncommentselection.h>
80 #include <extensionsystem/pluginmanager.h>
81 #include <projectexplorer/projectexplorerconstants.h>
82 #include <texteditor/basetextdocument.h>
83 #include <texteditor/basetextdocumentlayout.h>
84 #include <texteditor/fontsettings.h>
85 #include <texteditor/tabsettings.h>
86 #include <texteditor/texteditorconstants.h>
88 #include <QtCore/QDebug>
89 #include <QtCore/QTime>
90 #include <QtCore/QTimer>
91 #include <QtCore/QStack>
92 #include <QtCore/QSettings>
93 #include <QtCore/QSignalMapper>
94 #include <QtGui/QAction>
95 #include <QtGui/QApplication>
96 #include <QtGui/QHeaderView>
97 #include <QtGui/QLayout>
98 #include <QtGui/QMenu>
99 #include <QtGui/QShortcut>
100 #include <QtGui/QTextEdit>
101 #include <QtGui/QComboBox>
102 #include <QtGui/QToolBar>
103 #include <QtGui/QTreeView>
104 #include <QtGui/QSortFilterProxyModel>
109 UPDATE_OUTLINE_INTERVAL = 500,
110 UPDATE_USES_INTERVAL = 500
113 using namespace CPlusPlus;
114 using namespace CppEditor::Internal;
117 bool semanticHighlighterDisabled = qstrcmp(qVersion(), "4.7.0") == 0;
120 static QList<QTextEdit::ExtraSelection> createSelections(QTextDocument *document,
121 const QList<CPlusPlus::Document::DiagnosticMessage> &msgs,
122 const QTextCharFormat &format)
124 QList<QTextEdit::ExtraSelection> selections;
126 foreach (const Document::DiagnosticMessage &m, msgs) {
127 const int pos = document->findBlockByNumber(m.line() - 1).position() + m.column() - 1;
131 QTextCursor cursor(document);
132 cursor.setPosition(pos);
133 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, m.length());
135 QTextEdit::ExtraSelection sel;
138 sel.format.setToolTip(m.text());
139 selections.append(sel);
147 class OverviewTreeView : public QTreeView
150 OverviewTreeView(QWidget *parent = 0)
153 // TODO: Disable the root for all items (with a custom delegate?)
154 setRootIsDecorated(false);
160 setMinimumWidth(qMax(sizeHintForColumn(0), minimumSizeHint().width()));
164 class OverviewProxyModel : public QSortFilterProxyModel
168 OverviewProxyModel(CPlusPlus::OverviewModel *sourceModel, QObject *parent) :
169 QSortFilterProxyModel(parent),
170 m_sourceModel(sourceModel)
172 setSourceModel(m_sourceModel);
175 bool filterAcceptsRow(int sourceRow,const QModelIndex &sourceParent) const
177 // ignore generated symbols, e.g. by macro expansion (Q_OBJECT)
178 const QModelIndex sourceIndex = m_sourceModel->index(sourceRow, 0, sourceParent);
179 CPlusPlus::Symbol *symbol = m_sourceModel->symbolFromIndex(sourceIndex);
180 if (symbol && symbol->isGenerated())
183 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
186 CPlusPlus::OverviewModel *m_sourceModel;
189 class FunctionDefinitionUnderCursor: protected ASTVisitor
193 DeclarationAST *_functionDefinition;
196 FunctionDefinitionUnderCursor(TranslationUnit *translationUnit)
197 : ASTVisitor(translationUnit),
201 DeclarationAST *operator()(AST *ast, unsigned line, unsigned column)
203 _functionDefinition = 0;
207 return _functionDefinition;
211 virtual bool preVisit(AST *ast)
213 if (_functionDefinition)
216 else if (FunctionDefinitionAST *def = ast->asFunctionDefinition()) {
217 return checkDeclaration(def);
220 else if (ObjCMethodDeclarationAST *method = ast->asObjCMethodDeclaration()) {
221 if (method->function_body)
222 return checkDeclaration(method);
229 bool checkDeclaration(DeclarationAST *ast)
231 unsigned startLine, startColumn;
232 unsigned endLine, endColumn;
233 getTokenStartPosition(ast->firstToken(), &startLine, &startColumn);
234 getTokenEndPosition(ast->lastToken() - 1, &endLine, &endColumn);
236 if (_line > startLine || (_line == startLine && _column >= startColumn)) {
237 if (_line < endLine || (_line == endLine && _column < endColumn)) {
238 _functionDefinition = ast;
247 class FindFunctionDefinitions: protected SymbolVisitor
249 const Name *_declarationName;
250 QList<Function *> *_functions;
253 FindFunctionDefinitions()
254 : _declarationName(0),
258 void operator()(const Name *declarationName, Scope *globals,
259 QList<Function *> *functions)
261 _declarationName = declarationName;
262 _functions = functions;
264 for (unsigned i = 0; i < globals->memberCount(); ++i) {
265 accept(globals->memberAt(i));
270 using SymbolVisitor::visit;
272 virtual bool visit(Function *function)
274 const Name *name = function->name();
275 if (const QualifiedNameId *q = name->asQualifiedNameId())
278 if (_declarationName->isEqualTo(name))
279 _functions->append(function);
286 struct CanonicalSymbol
288 CPPEditorWidget *editor;
289 TypeOfExpression typeOfExpression;
292 CanonicalSymbol(CPPEditorWidget *editor, const SemanticInfo &info)
293 : editor(editor), info(info)
295 typeOfExpression.init(info.doc, info.snapshot);
298 const LookupContext &context() const
300 return typeOfExpression.context();
303 static inline bool isIdentifierChar(const QChar &ch)
305 return ch.isLetterOrNumber() || ch == QLatin1Char('_');
308 Scope *getScopeAndExpression(const QTextCursor &cursor, QString *code)
310 return getScopeAndExpression(editor, info, cursor, code);
313 static Scope *getScopeAndExpression(CPPEditorWidget *editor, const SemanticInfo &info,
314 const QTextCursor &cursor,
320 QTextCursor tc = cursor;
322 editor->convertPosition(tc.position(), &line, &col);
323 ++col; // 1-based line and 1-based column
325 QTextDocument *document = editor->document();
327 int pos = tc.position();
329 if (! isIdentifierChar(document->characterAt(pos)))
330 if (! (pos > 0 && isIdentifierChar(document->characterAt(pos - 1))))
333 while (isIdentifierChar(document->characterAt(pos)))
337 ExpressionUnderCursor expressionUnderCursor;
338 *code = expressionUnderCursor(tc);
339 return info.doc->scopeAt(line, col);
342 Symbol *operator()(const QTextCursor &cursor)
346 if (Scope *scope = getScopeAndExpression(cursor, &code))
347 return operator()(scope, code);
352 Symbol *operator()(Scope *scope, const QString &code)
354 return canonicalSymbol(scope, code, typeOfExpression);
357 static Symbol *canonicalSymbol(Scope *scope, const QString &code, TypeOfExpression &typeOfExpression)
359 const QList<LookupItem> results = typeOfExpression(code, scope, TypeOfExpression::Preprocess);
361 for (int i = results.size() - 1; i != -1; --i) {
362 const LookupItem &r = results.at(i);
363 Symbol *decl = r.declaration();
365 if (! (decl && decl->enclosingScope()))
368 if (Class *classScope = r.declaration()->enclosingScope()->asClass()) {
369 const Identifier *declId = decl->identifier();
370 const Identifier *classId = classScope->identifier();
372 if (classId && classId->isEqualTo(declId))
373 continue; // skip it, it's a ctor or a dtor.
375 else if (Function *funTy = r.declaration()->type()->asFunctionType()) {
376 if (funTy->isVirtual())
377 return r.declaration();
382 for (int i = 0; i < results.size(); ++i) {
383 const LookupItem &r = results.at(i);
386 return r.declaration();
395 int numberOfClosedEditors = 0;
397 } // end of anonymous namespace
399 CPPEditor::CPPEditor(CPPEditorWidget *editor)
400 : BaseTextEditor(editor)
402 m_context.add(CppEditor::Constants::C_CPPEDITOR);
403 m_context.add(ProjectExplorer::Constants::LANG_CXX);
404 m_context.add(TextEditor::Constants::C_TEXTEDITOR);
407 CPPEditorWidget::CPPEditorWidget(QWidget *parent)
408 : TextEditor::BaseTextEditorWidget(parent)
409 , m_currentRenameSelection(NoCurrentRenameSelection)
411 , m_inRenameChanged(false)
412 , m_firstRenameChange(false)
413 , m_objcEnabled(false)
415 m_initialized = false;
416 qRegisterMetaType<CppEditor::Internal::SemanticInfo>("CppEditor::Internal::SemanticInfo");
418 m_semanticHighlighter = new SemanticHighlighter(this);
419 m_semanticHighlighter->start();
421 setParenthesesMatchingEnabled(true);
422 setMarksVisible(true);
423 setCodeFoldingSupported(true);
424 setIndenter(new CppQtStyleIndenter);
425 setAutoCompleter(new CppAutoCompleter);
427 baseTextDocument()->setSyntaxHighlighter(new CppHighlighter);
429 m_modelManager = CppModelManagerInterface::instance();
431 if (m_modelManager) {
432 connect(m_modelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)),
433 this, SLOT(onDocumentUpdated(CPlusPlus::Document::Ptr)));
436 m_highlightRevision = 0;
437 m_nextHighlightBlockNumber = 0;
438 connect(&m_highlightWatcher, SIGNAL(resultsReadyAt(int,int)), SLOT(highlightSymbolUsages(int,int)));
439 connect(&m_highlightWatcher, SIGNAL(finished()), SLOT(finishHighlightSymbolUsages()));
441 m_referencesRevision = 0;
442 m_referencesCursorPosition = 0;
443 connect(&m_referencesWatcher, SIGNAL(finished()), SLOT(markSymbolsNow()));
446 CPPEditorWidget::~CPPEditorWidget()
448 if (Core::EditorManager *em = Core::EditorManager::instance())
449 em->hideEditorInfoBar(QLatin1String("CppEditor.Rename"));
451 m_semanticHighlighter->abort();
452 m_semanticHighlighter->wait();
454 ++numberOfClosedEditors;
455 if (numberOfClosedEditors == 5) {
456 m_modelManager->GC();
457 numberOfClosedEditors = 0;
461 TextEditor::BaseTextEditor *CPPEditorWidget::createEditor()
463 CPPEditor *editable = new CPPEditor(this);
464 createToolBar(editable);
468 void CPPEditorWidget::createToolBar(CPPEditor *editor)
470 m_outlineCombo = new QComboBox;
471 m_outlineCombo->setMinimumContentsLength(22);
473 // Make the combo box prefer to expand
474 QSizePolicy policy = m_outlineCombo->sizePolicy();
475 policy.setHorizontalPolicy(QSizePolicy::Expanding);
476 m_outlineCombo->setSizePolicy(policy);
478 QTreeView *outlineView = new OverviewTreeView;
479 outlineView->header()->hide();
480 outlineView->setItemsExpandable(false);
481 m_outlineCombo->setView(outlineView);
482 m_outlineCombo->setMaxVisibleItems(20);
484 m_outlineModel = new OverviewModel(this);
485 m_proxyModel = new OverviewProxyModel(m_outlineModel, this);
486 if (CppPlugin::instance()->sortedOutline())
487 m_proxyModel->sort(0, Qt::AscendingOrder);
489 m_proxyModel->sort(-1, Qt::AscendingOrder); // don't sort yet, but set column for sortedOutline()
490 m_proxyModel->setDynamicSortFilter(true);
491 m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
492 m_outlineCombo->setModel(m_proxyModel);
494 m_outlineCombo->setContextMenuPolicy(Qt::ActionsContextMenu);
495 m_sortAction = new QAction(tr("Sort Alphabetically"), m_outlineCombo);
496 m_sortAction->setCheckable(true);
497 m_sortAction->setChecked(sortedOutline());
498 connect(m_sortAction, SIGNAL(toggled(bool)), CppPlugin::instance(), SLOT(setSortedOutline(bool)));
499 m_outlineCombo->addAction(m_sortAction);
501 m_updateOutlineTimer = new QTimer(this);
502 m_updateOutlineTimer->setSingleShot(true);
503 m_updateOutlineTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
504 connect(m_updateOutlineTimer, SIGNAL(timeout()), this, SLOT(updateOutlineNow()));
506 m_updateOutlineIndexTimer = new QTimer(this);
507 m_updateOutlineIndexTimer->setSingleShot(true);
508 m_updateOutlineIndexTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
509 connect(m_updateOutlineIndexTimer, SIGNAL(timeout()), this, SLOT(updateOutlineIndexNow()));
511 m_updateUsesTimer = new QTimer(this);
512 m_updateUsesTimer->setSingleShot(true);
513 m_updateUsesTimer->setInterval(UPDATE_USES_INTERVAL);
514 connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow()));
516 connect(m_outlineCombo, SIGNAL(activated(int)), this, SLOT(jumpToOutlineElement(int)));
517 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateOutlineIndex()));
518 connect(m_outlineCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOutlineToolTip()));
519 connect(document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onContentsChanged(int,int,int)));
521 connect(file(), SIGNAL(changed()), this, SLOT(updateFileName()));
524 // set up the semantic highlighter
525 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateUses()));
526 connect(this, SIGNAL(textChanged()), this, SLOT(updateUses()));
528 connect(m_semanticHighlighter, SIGNAL(changed(CppEditor::Internal::SemanticInfo)),
529 this, SLOT(updateSemanticInfo(CppEditor::Internal::SemanticInfo)));
531 editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Left, m_outlineCombo);
534 void CPPEditorWidget::paste()
536 if (m_currentRenameSelection == NoCurrentRenameSelection) {
537 BaseTextEditorWidget::paste();
542 BaseTextEditorWidget::paste();
546 void CPPEditorWidget::cut()
548 if (m_currentRenameSelection == NoCurrentRenameSelection) {
549 BaseTextEditorWidget::cut();
554 BaseTextEditorWidget::cut();
558 CppModelManagerInterface *CPPEditorWidget::modelManager() const
560 return m_modelManager;
563 void CPPEditorWidget::setMimeType(const QString &mt)
565 BaseTextEditorWidget::setMimeType(mt);
566 setObjCEnabled(mt == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE);
569 void CPPEditorWidget::setObjCEnabled(bool onoff)
571 m_objcEnabled = onoff;
574 bool CPPEditorWidget::isObjCEnabled() const
575 { return m_objcEnabled; }
577 void CPPEditorWidget::startRename()
579 m_inRenameChanged = false;
582 void CPPEditorWidget::finishRename()
584 if (!m_inRenameChanged)
589 QTextCursor cursor = textCursor();
590 cursor.joinPreviousEditBlock();
592 cursor.setPosition(m_currentRenameSelectionEnd.position());
593 cursor.setPosition(m_currentRenameSelectionBegin.position(), QTextCursor::KeepAnchor);
594 m_renameSelections[m_currentRenameSelection].cursor = cursor;
595 QString text = cursor.selectedText();
597 for (int i = 0; i < m_renameSelections.size(); ++i) {
598 if (i == m_currentRenameSelection)
600 QTextEdit::ExtraSelection &s = m_renameSelections[i];
601 int pos = s.cursor.selectionStart();
602 s.cursor.removeSelectedText();
603 s.cursor.insertText(text);
604 s.cursor.setPosition(pos, QTextCursor::KeepAnchor);
607 setExtraSelections(CodeSemanticsSelection, m_renameSelections);
608 cursor.endEditBlock();
613 void CPPEditorWidget::abortRename()
615 if (m_currentRenameSelection <= NoCurrentRenameSelection)
617 m_renameSelections[m_currentRenameSelection].format = m_occurrencesFormat;
618 m_currentRenameSelection = NoCurrentRenameSelection;
619 m_currentRenameSelectionBegin = QTextCursor();
620 m_currentRenameSelectionEnd = QTextCursor();
621 setExtraSelections(CodeSemanticsSelection, m_renameSelections);
624 void CPPEditorWidget::rehighlight(bool force)
626 const SemanticHighlighter::Source source = currentSource(force);
627 m_semanticHighlighter->rehighlight(source);
630 void CPPEditorWidget::onDocumentUpdated(Document::Ptr doc)
632 if (doc->fileName() != file()->fileName())
635 if (doc->editorRevision() != editorRevision())
638 if (! m_initialized) {
639 m_initialized = true;
640 rehighlight(/* force = */ true);
643 m_updateOutlineTimer->start();
646 const Macro *CPPEditorWidget::findCanonicalMacro(const QTextCursor &cursor, Document::Ptr doc) const
652 convertPosition(cursor.position(), &line, &col);
654 if (const Macro *macro = doc->findMacroDefinitionAt(line))
657 if (const Document::MacroUse *use = doc->findMacroUseAt(cursor.position()))
658 return &use->macro();
663 void CPPEditorWidget::findUsages()
665 SemanticInfo info = m_lastSemanticInfo;
666 info.snapshot = CppModelManagerInterface::instance()->snapshot();
667 info.snapshot.insert(info.doc);
669 CanonicalSymbol cs(this, info);
670 Symbol *canonicalSymbol = cs(textCursor());
671 if (canonicalSymbol) {
672 m_modelManager->findUsages(canonicalSymbol, cs.context());
673 } else if (const Macro *macro = findCanonicalMacro(textCursor(), info.doc)) {
674 m_modelManager->findMacroUsages(*macro);
679 void CPPEditorWidget::renameUsagesNow(const QString &replacement)
681 SemanticInfo info = m_lastSemanticInfo;
682 info.snapshot = CppModelManagerInterface::instance()->snapshot();
683 info.snapshot.insert(info.doc);
685 CanonicalSymbol cs(this, info);
686 if (Symbol *canonicalSymbol = cs(textCursor())) {
687 if (canonicalSymbol->identifier() != 0) {
688 if (showWarningMessage()) {
689 Core::EditorManager::instance()->showEditorInfoBar(QLatin1String("CppEditor.Rename"),
690 tr("This change cannot be undone."),
691 tr("Yes, I know what I am doing."),
692 this, SLOT(hideRenameNotification()));
695 m_modelManager->renameUsages(canonicalSymbol, cs.context(), replacement);
700 void CPPEditorWidget::renameUsages()
705 bool CPPEditorWidget::showWarningMessage() const
708 QSettings *settings = Core::ICore::instance()->settings();
709 settings->beginGroup(QLatin1String("CppEditor"));
710 settings->beginGroup(QLatin1String("Rename"));
711 const bool showWarningMessage = settings->value(QLatin1String("ShowWarningMessage"), true).toBool();
712 settings->endGroup();
713 settings->endGroup();
714 return showWarningMessage;
717 void CPPEditorWidget::setShowWarningMessage(bool showWarningMessage)
720 QSettings *settings = Core::ICore::instance()->settings();
721 settings->beginGroup(QLatin1String("CppEditor"));
722 settings->beginGroup(QLatin1String("Rename"));
723 settings->setValue(QLatin1String("ShowWarningMessage"), showWarningMessage);
724 settings->endGroup();
725 settings->endGroup();
728 void CPPEditorWidget::hideRenameNotification()
730 setShowWarningMessage(false);
731 Core::EditorManager::instance()->hideEditorInfoBar(QLatin1String("CppEditor.Rename"));
734 void CPPEditorWidget::markSymbolsNow()
736 if (m_references.isCanceled())
738 else if (m_referencesCursorPosition != position())
740 else if (m_referencesRevision != editorRevision())
743 const SemanticInfo info = m_lastSemanticInfo;
744 TranslationUnit *unit = info.doc->translationUnit();
745 const QList<int> result = m_references.result();
747 QList<QTextEdit::ExtraSelection> selections;
749 foreach (int index, result) {
750 unsigned line, column;
751 unit->getTokenPosition(index, &line, &column);
754 --column; // adjust the column position.
756 const int len = unit->tokenAt(index).f.length;
758 QTextCursor cursor(document()->findBlockByNumber(line - 1));
759 cursor.setPosition(cursor.position() + column);
760 cursor.setPosition(cursor.position() + len, QTextCursor::KeepAnchor);
762 QTextEdit::ExtraSelection sel;
763 sel.format = m_occurrencesFormat;
765 selections.append(sel);
769 setExtraSelections(CodeSemanticsSelection, selections);
772 static QList<int> lazyFindReferences(Scope *scope, QString code, Document::Ptr doc, Snapshot snapshot)
774 TypeOfExpression typeOfExpression;
775 snapshot.insert(doc);
776 typeOfExpression.init(doc, snapshot);
777 if (Symbol *canonicalSymbol = CanonicalSymbol::canonicalSymbol(scope, code, typeOfExpression)) {
778 return CppModelManagerInterface::instance()->references(canonicalSymbol, typeOfExpression.context());
783 void CPPEditorWidget::markSymbols(const QTextCursor &tc, const SemanticInfo &info)
790 CanonicalSymbol cs(this, info);
792 if (Scope *scope = cs.getScopeAndExpression(this, info, tc, &expression)) {
793 m_references.cancel();
794 m_referencesRevision = info.revision;
795 m_referencesCursorPosition = position();
796 m_references = QtConcurrent::run(&lazyFindReferences, scope, expression, info.doc, info.snapshot);
797 m_referencesWatcher.setFuture(m_references);
799 const QList<QTextEdit::ExtraSelection> selections = extraSelections(CodeSemanticsSelection);
801 if (! selections.isEmpty())
802 setExtraSelections(CodeSemanticsSelection, QList<QTextEdit::ExtraSelection>());
806 void CPPEditorWidget::renameSymbolUnderCursor()
808 updateSemanticInfo(m_semanticHighlighter->semanticInfo(currentSource()));
811 QTextCursor c = textCursor();
813 for (int i = 0; i < m_renameSelections.size(); ++i) {
814 QTextEdit::ExtraSelection s = m_renameSelections.at(i);
815 if (c.position() >= s.cursor.anchor()
816 && c.position() <= s.cursor.position()) {
817 m_currentRenameSelection = i;
818 m_firstRenameChange = true;
819 m_currentRenameSelectionBegin = QTextCursor(c.document()->docHandle(),
820 m_renameSelections[i].cursor.selectionStart());
821 m_currentRenameSelectionEnd = QTextCursor(c.document()->docHandle(),
822 m_renameSelections[i].cursor.selectionEnd());
823 m_renameSelections[i].format = m_occurrenceRenameFormat;
824 setExtraSelections(CodeSemanticsSelection, m_renameSelections);
829 if (m_renameSelections.isEmpty())
833 void CPPEditorWidget::onContentsChanged(int position, int charsRemoved, int charsAdded)
837 if (m_currentRenameSelection == NoCurrentRenameSelection || m_inRename)
840 if (position + charsAdded == m_currentRenameSelectionBegin.position()) {
841 // we are inserting at the beginning of the rename selection => expand
842 m_currentRenameSelectionBegin.setPosition(position);
843 m_renameSelections[m_currentRenameSelection].cursor.setPosition(position, QTextCursor::KeepAnchor);
846 // the condition looks odd, but keep in mind that the begin and end cursors do move automatically
847 m_inRenameChanged = (position >= m_currentRenameSelectionBegin.position()
848 && position + charsAdded <= m_currentRenameSelectionEnd.position());
850 if (!m_inRenameChanged)
853 if (charsRemoved > 0)
857 void CPPEditorWidget::updateFileName()
860 void CPPEditorWidget::jumpToOutlineElement(int)
862 QModelIndex index = m_proxyModel->mapToSource(m_outlineCombo->view()->currentIndex());
863 Symbol *symbol = m_outlineModel->symbolFromIndex(index);
867 openCppEditorAt(linkToSymbol(symbol));
870 void CPPEditorWidget::setSortedOutline(bool sort)
872 if (sort != sortedOutline()) {
874 m_proxyModel->sort(0, Qt::AscendingOrder);
876 m_proxyModel->sort(-1, Qt::AscendingOrder);
877 bool block = m_sortAction->blockSignals(true);
878 m_sortAction->setChecked(m_proxyModel->sortColumn() == 0);
879 m_sortAction->blockSignals(block);
880 updateOutlineIndexNow();
884 bool CPPEditorWidget::sortedOutline() const
886 return (m_proxyModel->sortColumn() == 0);
889 void CPPEditorWidget::updateOutlineNow()
891 const Snapshot snapshot = m_modelManager->snapshot();
892 Document::Ptr document = snapshot.document(file()->fileName());
897 if (document->editorRevision() != editorRevision()) {
898 m_updateOutlineTimer->start();
902 m_outlineModel->rebuild(document);
904 OverviewTreeView *treeView = static_cast<OverviewTreeView *>(m_outlineCombo->view());
906 updateOutlineIndexNow();
909 void CPPEditorWidget::updateOutlineIndex()
911 m_updateOutlineIndexTimer->start();
914 void CPPEditorWidget::highlightUses(const QList<SemanticInfo::Use> &uses,
915 const SemanticInfo &semanticInfo,
916 QList<QTextEdit::ExtraSelection> *selections)
918 bool isUnused = false;
920 if (uses.size() == 1)
923 foreach (const SemanticInfo::Use &use, uses) {
924 QTextEdit::ExtraSelection sel;
927 sel.format = m_occurrencesUnusedFormat;
929 sel.format = m_occurrencesFormat;
931 const int anchor = document()->findBlockByNumber(use.line - 1).position() + use.column - 1;
932 const int position = anchor + use.length;
934 sel.cursor = QTextCursor(document());
935 sel.cursor.setPosition(anchor);
936 sel.cursor.setPosition(position, QTextCursor::KeepAnchor);
939 if (semanticInfo.hasQ && sel.cursor.selectedText() == QLatin1String("q"))
942 else if (semanticInfo.hasD && sel.cursor.selectedText() == QLatin1String("d"))
946 selections->append(sel);
950 void CPPEditorWidget::updateOutlineIndexNow()
952 if (!m_outlineModel->document())
955 if (m_outlineModel->document()->editorRevision() != editorRevision()) {
956 m_updateOutlineIndexTimer->start();
960 m_updateOutlineIndexTimer->stop();
962 m_outlineModelIndex = QModelIndex(); //invalidate
963 QModelIndex comboIndex = outlineModelIndex();
966 if (comboIndex.isValid()) {
967 bool blocked = m_outlineCombo->blockSignals(true);
969 // There is no direct way to select a non-root item
970 m_outlineCombo->setRootModelIndex(m_proxyModel->mapFromSource(comboIndex.parent()));
971 m_outlineCombo->setCurrentIndex(m_proxyModel->mapFromSource(comboIndex).row());
972 m_outlineCombo->setRootModelIndex(QModelIndex());
974 updateOutlineToolTip();
976 m_outlineCombo->blockSignals(blocked);
980 void CPPEditorWidget::updateOutlineToolTip()
982 m_outlineCombo->setToolTip(m_outlineCombo->currentText());
985 void CPPEditorWidget::updateUses()
987 if (editorRevision() != m_highlightRevision)
988 m_highlighter.cancel();
989 m_updateUsesTimer->start();
992 void CPPEditorWidget::updateUsesNow()
994 if (m_currentRenameSelection != NoCurrentRenameSelection)
997 semanticRehighlight();
1000 void CPPEditorWidget::highlightSymbolUsages(int from, int to)
1002 if (editorRevision() != m_highlightRevision)
1005 else if (m_highlighter.isCanceled())
1008 CppHighlighter *highlighter = qobject_cast<CppHighlighter*>(baseTextDocument()->syntaxHighlighter());
1009 Q_ASSERT(highlighter);
1010 QTextDocument *doc = document();
1012 if (m_nextHighlightBlockNumber >= doc->blockCount())
1015 QMap<int, QVector<SemanticInfo::Use> > chunks = CheckSymbols::chunks(m_highlighter, from, to);
1016 if (chunks.isEmpty())
1019 QTextBlock b = doc->findBlockByNumber(m_nextHighlightBlockNumber);
1021 QMapIterator<int, QVector<SemanticInfo::Use> > it(chunks);
1022 while (b.isValid() && it.hasNext()) {
1024 const int blockNumber = it.key();
1025 Q_ASSERT(blockNumber < doc->blockCount());
1027 while (m_nextHighlightBlockNumber < blockNumber) {
1028 highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>());
1030 ++m_nextHighlightBlockNumber;
1033 QList<QTextLayout::FormatRange> formats;
1034 foreach (const SemanticInfo::Use &use, it.value()) {
1035 QTextLayout::FormatRange formatRange;
1038 case SemanticInfo::Use::Type:
1039 formatRange.format = m_typeFormat;
1042 case SemanticInfo::Use::Field:
1043 formatRange.format = m_fieldFormat;
1046 case SemanticInfo::Use::Local:
1047 formatRange.format = m_localFormat;
1050 case SemanticInfo::Use::Static:
1051 formatRange.format = m_staticFormat;
1054 case SemanticInfo::Use::VirtualMethod:
1055 formatRange.format = m_virtualMethodFormat;
1062 formatRange.start = use.column - 1;
1063 formatRange.length = use.length;
1064 formats.append(formatRange);
1066 highlighter->setExtraAdditionalFormats(b, formats);
1068 ++m_nextHighlightBlockNumber;
1072 void CPPEditorWidget::finishHighlightSymbolUsages()
1074 if (editorRevision() != m_highlightRevision)
1077 else if (m_highlighter.isCanceled())
1080 CppHighlighter *highlighter = qobject_cast<CppHighlighter*>(baseTextDocument()->syntaxHighlighter());
1081 Q_ASSERT(highlighter);
1082 QTextDocument *doc = document();
1084 if (m_nextHighlightBlockNumber >= doc->blockCount())
1087 QTextBlock b = doc->findBlockByNumber(m_nextHighlightBlockNumber);
1089 while (b.isValid()) {
1090 highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>());
1092 ++m_nextHighlightBlockNumber;
1097 void CPPEditorWidget::switchDeclarationDefinition()
1099 if (! m_modelManager)
1102 const Snapshot snapshot = m_modelManager->snapshot();
1104 if (Document::Ptr thisDocument = snapshot.document(file()->fileName())) {
1105 int line = 0, positionInBlock = 0;
1106 convertPosition(position(), &line, &positionInBlock);
1108 Symbol *lastVisibleSymbol = thisDocument->lastVisibleSymbolAt(line, positionInBlock + 1);
1109 if (! lastVisibleSymbol)
1112 Function *function = lastVisibleSymbol->asFunction();
1114 function = lastVisibleSymbol->enclosingFunction();
1117 LookupContext context(thisDocument, snapshot);
1119 Function *functionDefinition = function->asFunction();
1120 ClassOrNamespace *binding = context.lookupType(functionDefinition);
1122 const QList<LookupItem> declarations = context.lookup(functionDefinition->name(), functionDefinition->enclosingScope());
1123 QList<Symbol *> best;
1124 foreach (const LookupItem &r, declarations) {
1125 if (Symbol *decl = r.declaration()) {
1126 if (Function *funTy = decl->type()->asFunctionType()) {
1127 if (funTy->isEqualTo(function) && decl != function && binding == r.binding())
1134 if (! best.isEmpty())
1135 openCppEditorAt(linkToSymbol(best.first()));
1137 } else if (lastVisibleSymbol && lastVisibleSymbol->isDeclaration() && lastVisibleSymbol->type()->isFunctionType()) {
1138 if (Symbol *def = snapshot.findMatchingDefinition(lastVisibleSymbol))
1139 openCppEditorAt(linkToSymbol(def));
1144 static inline LookupItem skipForwardDeclarations(const QList<LookupItem> &resolvedSymbols)
1146 QList<LookupItem> candidates = resolvedSymbols;
1148 LookupItem result = candidates.first();
1149 const FullySpecifiedType ty = result.type().simplified();
1151 if (ty->isForwardClassDeclarationType()) {
1152 while (! candidates.isEmpty()) {
1153 LookupItem r = candidates.takeFirst();
1155 if (! r.type()->isForwardClassDeclarationType()) {
1162 if (ty->isObjCForwardClassDeclarationType()) {
1163 while (! candidates.isEmpty()) {
1164 LookupItem r = candidates.takeFirst();
1166 if (! r.type()->isObjCForwardClassDeclarationType()) {
1173 if (ty->isObjCForwardProtocolDeclarationType()) {
1174 while (! candidates.isEmpty()) {
1175 LookupItem r = candidates.takeFirst();
1177 if (! r.type()->isObjCForwardProtocolDeclarationType()) {
1189 QList<Declaration *> findMatchingDeclaration(const LookupContext &context,
1190 Function *functionType)
1192 QList<Declaration *> result;
1194 Scope *enclosingScope = functionType->enclosingScope();
1195 while (! (enclosingScope->isNamespace() || enclosingScope->isClass()))
1196 enclosingScope = enclosingScope->enclosingScope();
1197 Q_ASSERT(enclosingScope != 0);
1199 const Name *functionName = functionType->name();
1201 return result; // anonymous function names are not valid c++
1203 ClassOrNamespace *binding = 0;
1204 const QualifiedNameId *qName = functionName->asQualifiedNameId();
1207 binding = context.lookupType(qName->base(), enclosingScope);
1208 functionName = qName->name();
1211 if (!binding) { // declaration for a global function
1212 binding = context.lookupType(enclosingScope);
1218 const Identifier *funcId = functionName->identifier();
1219 if (!funcId) // E.g. operator, which we might be able to handle in the future...
1222 QList<Declaration *> good, better, best;
1224 foreach (Symbol *s, binding->symbols()) {
1225 Class *matchingClass = s->asClass();
1229 for (Symbol *s = matchingClass->find(funcId); s; s = s->next()) {
1232 else if (! funcId->isEqualTo(s->identifier()))
1234 else if (! s->type()->isFunctionType())
1236 else if (Declaration *decl = s->asDeclaration()) {
1237 if (Function *declFunTy = decl->type()->asFunctionType()) {
1238 if (functionType->isEqualTo(declFunTy))
1240 else if (functionType->argumentCount() == declFunTy->argumentCount() && result.isEmpty())
1241 better.prepend(decl);
1249 result.append(best);
1250 result.append(better);
1251 result.append(good);
1256 } // end of anonymous namespace
1258 CPPEditorWidget::Link CPPEditorWidget::attemptFuncDeclDef(const QTextCursor &cursor, const Document::Ptr &doc, Snapshot snapshot) const
1260 snapshot.insert(doc);
1264 QList<AST *> path = ASTPath(doc)(cursor);
1266 if (path.size() < 5)
1269 NameAST *name = path.last()->asName();
1273 if (QualifiedNameAST *qName = path.at(path.size() - 2)->asQualifiedName()) {
1274 // TODO: check which part of the qualified name we're on
1275 if (qName->unqualified_name != name)
1279 for (int i = path.size() - 1; i != -1; --i) {
1280 AST *node = path.at(i);
1282 if (node->asParameterDeclaration() != 0)
1286 AST *declParent = 0;
1287 DeclaratorAST *decl = 0;
1288 for (int i = path.size() - 2; i > 0; --i) {
1289 if ((decl = path.at(i)->asDeclarator()) != 0) {
1290 declParent = path.at(i - 1);
1294 if (!decl || !declParent)
1296 if (!decl->postfix_declarator_list || !decl->postfix_declarator_list->value)
1298 FunctionDeclaratorAST *funcDecl = decl->postfix_declarator_list->value->asFunctionDeclarator();
1303 if (FunctionDefinitionAST *funDef = declParent->asFunctionDefinition()) {
1304 QList<Declaration *> candidates = findMatchingDeclaration(LookupContext(doc, snapshot),
1306 if (!candidates.isEmpty()) // TODO: improve disambiguation
1307 target = candidates.first();
1308 } else if (declParent->asSimpleDeclaration()) {
1309 target = snapshot.findMatchingDefinition(funcDecl->symbol);
1313 result = linkToSymbol(target);
1315 unsigned startLine, startColumn, endLine, endColumn;
1316 doc->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine, &startColumn);
1317 doc->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine, &endColumn);
1319 QTextDocument *textDocument = cursor.document();
1320 result.begin = textDocument->findBlockByNumber(startLine - 1).position() + startColumn - 1;
1321 result.end = textDocument->findBlockByNumber(endLine - 1).position() + endColumn - 1;
1327 CPPEditorWidget::Link CPPEditorWidget::findMacroLink(const QByteArray &name) const
1329 if (! name.isEmpty()) {
1330 if (Document::Ptr doc = m_lastSemanticInfo.doc) {
1331 const Snapshot snapshot = m_modelManager->snapshot();
1332 QSet<QString> processed;
1333 return findMacroLink(name, doc, snapshot, &processed);
1340 CPPEditorWidget::Link CPPEditorWidget::findMacroLink(const QByteArray &name,
1342 const Snapshot &snapshot,
1343 QSet<QString> *processed) const
1345 if (doc && ! name.startsWith('<') && ! processed->contains(doc->fileName())) {
1346 processed->insert(doc->fileName());
1348 foreach (const Macro ¯o, doc->definedMacros()) {
1349 if (macro.name() == name) {
1351 link.fileName = macro.fileName();
1352 link.line = macro.line();
1357 const QList<Document::Include> includes = doc->includes();
1358 for (int index = includes.size() - 1; index != -1; --index) {
1359 const Document::Include &i = includes.at(index);
1360 Link link = findMacroLink(name, snapshot.document(i.fileName()), snapshot, processed);
1361 if (! link.fileName.isEmpty())
1369 QString CPPEditorWidget::identifierUnderCursor(QTextCursor *macroCursor) const
1371 macroCursor->movePosition(QTextCursor::StartOfWord);
1372 macroCursor->movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1373 return macroCursor->selectedText();
1376 CPPEditorWidget::Link CPPEditorWidget::findLinkAt(const QTextCursor &cursor,
1381 if (!m_modelManager)
1384 const Snapshot snapshot = m_modelManager->snapshot();
1386 if (m_lastSemanticInfo.doc){
1387 Link l = attemptFuncDeclDef(cursor, m_lastSemanticInfo.doc, snapshot);
1393 int lineNumber = 0, positionInBlock = 0;
1394 convertPosition(cursor.position(), &lineNumber, &positionInBlock);
1395 Document::Ptr doc = snapshot.document(file()->fileName());
1399 const unsigned line = lineNumber;
1400 const unsigned column = positionInBlock + 1;
1402 QTextCursor tc = cursor;
1404 // Make sure we're not at the start of a word
1406 const QChar c = characterAt(tc.position());
1407 if (c.isLetter() || c == QLatin1Char('_'))
1408 tc.movePosition(QTextCursor::Right);
1412 int beginOfToken = 0;
1415 SimpleLexer tokenize;
1416 tokenize.setQtMocRunEnabled(true);
1417 const QString blockText = cursor.block().text();
1418 const QList<Token> tokens = tokenize(blockText, BackwardsScanner::previousBlockState(cursor.block()));
1420 bool recognizedQtMethod = false;
1422 for (int i = 0; i < tokens.size(); ++i) {
1423 const Token &tk = tokens.at(i);
1425 if (((unsigned) positionInBlock) >= tk.begin() && ((unsigned) positionInBlock) <= tk.end()) {
1426 if (i >= 2 && tokens.at(i).is(T_IDENTIFIER) && tokens.at(i - 1).is(T_LPAREN)
1427 && (tokens.at(i - 2).is(T_SIGNAL) || tokens.at(i - 2).is(T_SLOT))) {
1429 // token[i] == T_IDENTIFIER
1430 // token[i + 1] == T_LPAREN
1431 // token[.....] == ....
1432 // token[i + n] == T_RPAREN
1434 if (i + 1 < tokens.size() && tokens.at(i + 1).is(T_LPAREN)) {
1435 // skip matched parenthesis
1439 for (; j < tokens.size(); ++j) {
1440 if (tokens.at(j).is(T_LPAREN))
1443 else if (tokens.at(j).is(T_RPAREN)) {
1449 if (j < tokens.size()) {
1450 QTextBlock block = cursor.block();
1452 beginOfToken = block.position() + tokens.at(i).begin();
1453 endOfToken = block.position() + tokens.at(i).end();
1455 tc.setPosition(block.position() + tokens.at(j).end());
1456 recognizedQtMethod = true;
1464 if (! recognizedQtMethod) {
1465 const QTextBlock block = tc.block();
1466 int pos = cursor.positionInBlock();
1467 QChar ch = document()->characterAt(cursor.position());
1468 if (pos > 0 && ! (ch.isLetterOrNumber() || ch == QLatin1Char('_')))
1469 --pos; // positionInBlock points to a delimiter character.
1470 const Token tk = SimpleLexer::tokenAt(block.text(), pos, BackwardsScanner::previousBlockState(block), true);
1472 beginOfToken = block.position() + tk.begin();
1473 endOfToken = block.position() + tk.end();
1475 // Handle include directives
1476 if (tk.is(T_STRING_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL)) {
1477 const unsigned lineno = cursor.blockNumber() + 1;
1478 foreach (const Document::Include &incl, doc->includes()) {
1479 if (incl.line() == lineno && incl.resolved()) {
1480 link.fileName = incl.fileName();
1481 link.begin = beginOfToken + 1;
1482 link.end = endOfToken - 1;
1488 if (tk.isNot(T_IDENTIFIER) && tk.kind() < T_FIRST_QT_KEYWORD && tk.kind() > T_LAST_KEYWORD)
1491 tc.setPosition(endOfToken);
1494 // Find the last symbol up to the cursor position
1495 Scope *scope = doc->scopeAt(line, column);
1499 // Evaluate the type of the expression under the cursor
1500 ExpressionUnderCursor expressionUnderCursor;
1501 QString expression = expressionUnderCursor(tc);
1503 for (int pos = tc.position();; ++pos) {
1504 const QChar ch = characterAt(pos);
1508 if (ch == QLatin1Char('(') && ! expression.isEmpty()) {
1509 tc.setPosition(pos);
1510 if (TextEditor::TextBlockUserData::findNextClosingParenthesis(&tc, true)) {
1511 expression.append(tc.selectedText());
1519 TypeOfExpression typeOfExpression;
1520 typeOfExpression.init(doc, snapshot);
1521 const QList<LookupItem> resolvedSymbols = typeOfExpression.reference(expression, scope, TypeOfExpression::Preprocess);
1523 if (!resolvedSymbols.isEmpty()) {
1524 LookupItem result = skipForwardDeclarations(resolvedSymbols);
1526 foreach (const LookupItem &r, resolvedSymbols) {
1527 if (Symbol *d = r.declaration()) {
1528 if (d->isDeclaration() || d->isFunction()) {
1529 if (file()->fileName() == QString::fromUtf8(d->fileName(), d->fileNameLength())) {
1530 if (unsigned(lineNumber) == d->line() && unsigned(positionInBlock) >= d->column()) { // ### TODO: check the end
1531 result = r; // take the symbol under cursor.
1539 if (Symbol *symbol = result.declaration()) {
1542 if (resolveTarget) {
1543 Symbol *lastVisibleSymbol = doc->lastVisibleSymbolAt(line, column);
1545 def = findDefinition(symbol, snapshot);
1547 if (def == lastVisibleSymbol)
1548 def = 0; // jump to declaration then.
1550 if (symbol->isForwardClassDeclaration()) {
1551 def = snapshot.findMatchingClassDeclaration(symbol);
1555 link = linkToSymbol(def ? def : symbol);
1556 link.begin = beginOfToken;
1557 link.end = endOfToken;
1562 // Handle macro uses
1563 QTextCursor macroCursor = cursor;
1564 const QByteArray name = identifierUnderCursor(¯oCursor).toLatin1();
1565 link = findMacroLink(name);
1566 if (! link.fileName.isEmpty()) {
1567 link.begin = macroCursor.selectionStart();
1568 link.end = macroCursor.selectionEnd();
1575 void CPPEditorWidget::jumpToDefinition()
1577 openLink(findLinkAt(textCursor()));
1580 Symbol *CPPEditorWidget::findDefinition(Symbol *symbol, const Snapshot &snapshot) const
1582 if (symbol->isFunction())
1583 return 0; // symbol is a function definition.
1585 else if (! symbol->type()->isFunctionType())
1586 return 0; // not a function declaration
1588 return snapshot.findMatchingDefinition(symbol);
1591 unsigned CPPEditorWidget::editorRevision() const
1593 return document()->revision();
1596 bool CPPEditorWidget::isOutdated() const
1598 if (m_lastSemanticInfo.revision != editorRevision())
1604 SemanticInfo CPPEditorWidget::semanticInfo() const
1606 return m_lastSemanticInfo;
1609 CPlusPlus::OverviewModel *CPPEditorWidget::outlineModel() const
1611 return m_outlineModel;
1614 QModelIndex CPPEditorWidget::outlineModelIndex()
1616 if (!m_outlineModelIndex.isValid()) {
1617 int line = 0, column = 0;
1618 convertPosition(position(), &line, &column);
1619 m_outlineModelIndex = indexForPosition(line, column);
1620 emit outlineModelIndexChanged(m_outlineModelIndex);
1623 return m_outlineModelIndex;
1626 bool CPPEditorWidget::event(QEvent *e)
1628 switch (e->type()) {
1629 case QEvent::ShortcutOverride:
1630 if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_currentRenameSelection != NoCurrentRenameSelection) {
1639 return BaseTextEditorWidget::event(e);
1642 void CPPEditorWidget::performQuickFix(int index)
1644 TextEditor::QuickFixOperation::Ptr op = m_quickFixes.at(index);
1648 void CPPEditorWidget::contextMenuEvent(QContextMenuEvent *e)
1651 // updateSemanticInfo(m_semanticHighlighter->semanticInfo(currentSource()));
1653 QMenu *menu = new QMenu;
1655 Core::ActionManager *am = Core::ICore::instance()->actionManager();
1656 Core::ActionContainer *mcontext = am->actionContainer(Constants::M_CONTEXT);
1657 QMenu *contextMenu = mcontext->menu();
1659 QMenu *quickFixMenu = new QMenu(tr("&Refactor"), menu);
1660 quickFixMenu->addAction(am->command(Constants::RENAME_SYMBOL_UNDER_CURSOR)->action());
1662 CppQuickFixCollector *quickFixCollector = CppPlugin::instance()->quickFixCollector();
1663 QSignalMapper mapper;
1664 connect(&mapper, SIGNAL(mapped(int)), this, SLOT(performQuickFix(int)));
1666 if (! isOutdated()) {
1667 if (quickFixCollector->startCompletion(editor()) != -1) {
1668 m_quickFixes = quickFixCollector->quickFixes();
1670 if (! m_quickFixes.isEmpty())
1671 quickFixMenu->addSeparator();
1673 for (int index = 0; index < m_quickFixes.size(); ++index) {
1674 TextEditor::QuickFixOperation::Ptr op = m_quickFixes.at(index);
1675 QAction *action = quickFixMenu->addAction(op->description());
1676 mapper.setMapping(action, index);
1677 connect(action, SIGNAL(triggered()), &mapper, SLOT(map()));
1682 foreach (QAction *action, contextMenu->actions()) {
1683 menu->addAction(action);
1684 if (action->objectName() == Constants::M_REFACTORING_MENU_INSERTION_POINT)
1685 menu->addMenu(quickFixMenu);
1688 appendStandardContextMenuActions(menu);
1690 menu->exec(e->globalPos());
1691 quickFixCollector->cleanup();
1692 m_quickFixes.clear();
1696 void CPPEditorWidget::keyPressEvent(QKeyEvent *e)
1698 if (m_currentRenameSelection == NoCurrentRenameSelection) {
1699 TextEditor::BaseTextEditorWidget::keyPressEvent(e);
1703 QTextCursor cursor = textCursor();
1704 const QTextCursor::MoveMode moveMode =
1705 (e->modifiers() & Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
1709 case Qt::Key_Return:
1710 case Qt::Key_Escape:
1714 case Qt::Key_Home: {
1715 // Send home to start of name when within the name and not at the start
1716 if (cursor.position() > m_currentRenameSelectionBegin.position()
1717 && cursor.position() <= m_currentRenameSelectionEnd.position()) {
1718 cursor.setPosition(m_currentRenameSelectionBegin.position(), moveMode);
1719 setTextCursor(cursor);
1726 // Send end to end of name when within the name and not at the end
1727 if (cursor.position() >= m_currentRenameSelectionBegin.position()
1728 && cursor.position() < m_currentRenameSelectionEnd.position()) {
1729 cursor.setPosition(m_currentRenameSelectionEnd.position(), moveMode);
1730 setTextCursor(cursor);
1736 case Qt::Key_Backspace: {
1737 if (cursor.position() == m_currentRenameSelectionBegin.position()
1738 && !cursor.hasSelection()) {
1739 // Eat backspace at start of name when there is no selection
1745 case Qt::Key_Delete: {
1746 if (cursor.position() == m_currentRenameSelectionEnd.position()
1747 && !cursor.hasSelection()) {
1748 // Eat delete at end of name when there is no selection
1761 bool wantEditBlock = (cursor.position() >= m_currentRenameSelectionBegin.position()
1762 && cursor.position() <= m_currentRenameSelectionEnd.position());
1764 if (wantEditBlock) {
1765 // possible change inside rename selection
1766 if (m_firstRenameChange)
1767 cursor.beginEditBlock();
1769 cursor.joinPreviousEditBlock();
1770 m_firstRenameChange = false;
1772 TextEditor::BaseTextEditorWidget::keyPressEvent(e);
1774 cursor.endEditBlock();
1778 Core::Context CPPEditor::context() const
1783 Core::IEditor *CPPEditor::duplicate(QWidget *parent)
1785 CPPEditorWidget *newEditor = new CPPEditorWidget(parent);
1786 newEditor->duplicateFrom(editorWidget());
1787 CppPlugin::instance()->initializeEditor(newEditor);
1788 return newEditor->editor();
1791 QString CPPEditor::id() const
1793 return QLatin1String(CppEditor::Constants::CPPEDITOR_ID);
1796 bool CPPEditor::open(const QString & fileName)
1798 bool b = TextEditor::BaseTextEditor::open(fileName);
1799 editorWidget()->setMimeType(Core::ICore::instance()->mimeDatabase()->findByFile(QFileInfo(fileName)).type());
1803 void CPPEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
1805 TextEditor::BaseTextEditorWidget::setFontSettings(fs);
1806 CppHighlighter *highlighter = qobject_cast<CppHighlighter*>(baseTextDocument()->syntaxHighlighter());
1810 const QVector<QTextCharFormat> formats = fs.toTextCharFormats(highlighterFormatCategories());
1811 highlighter->setFormats(formats.constBegin(), formats.constEnd());
1813 m_occurrencesFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES));
1814 m_occurrencesUnusedFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES_UNUSED));
1815 m_occurrencesUnusedFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
1816 m_occurrencesUnusedFormat.setUnderlineColor(m_occurrencesUnusedFormat.foreground().color());
1817 m_occurrencesUnusedFormat.clearForeground();
1818 m_occurrencesUnusedFormat.setToolTip(tr("Unused variable"));
1819 m_occurrenceRenameFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES_RENAME));
1820 m_typeFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TYPE));
1821 m_localFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_LOCAL));
1822 m_fieldFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_FIELD));
1823 m_staticFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_STATIC));
1824 m_virtualMethodFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_VIRTUAL_METHOD));
1825 m_keywordFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_KEYWORD));
1827 // only set the background, we do not want to modify foreground properties set by the syntax highlighter or the link
1828 m_occurrencesFormat.clearForeground();
1829 m_occurrenceRenameFormat.clearForeground();
1831 // Clear all additional formats since they may have changed
1832 QTextBlock b = document()->firstBlock();
1833 while (b.isValid()) {
1834 highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>());
1838 // This also triggers an update of the additional formats
1839 highlighter->rehighlight();
1842 void CPPEditorWidget::setTabSettings(const TextEditor::TabSettings &ts)
1844 CppTools::QtStyleCodeFormatter formatter;
1845 formatter.invalidateCache(document());
1847 TextEditor::BaseTextEditorWidget::setTabSettings(ts);
1850 void CPPEditorWidget::unCommentSelection()
1852 Utils::unCommentSelection(this);
1855 CPPEditorWidget::Link CPPEditorWidget::linkToSymbol(CPlusPlus::Symbol *symbol)
1860 const QString fileName = QString::fromUtf8(symbol->fileName(),
1861 symbol->fileNameLength());
1862 unsigned line = symbol->line();
1863 unsigned column = symbol->column();
1868 if (symbol->isGenerated())
1871 return Link(fileName, line, column);
1874 bool CPPEditorWidget::openCppEditorAt(const Link &link)
1876 if (link.fileName.isEmpty())
1879 if (baseTextDocument()->fileName() == link.fileName) {
1880 Core::EditorManager *editorManager = Core::EditorManager::instance();
1881 editorManager->cutForwardNavigationHistory();
1882 editorManager->addCurrentPositionToNavigationHistory();
1883 gotoLine(link.line, link.column);
1888 return TextEditor::BaseTextEditorWidget::openEditorAt(link.fileName,
1891 Constants::CPPEDITOR_ID);
1894 void CPPEditorWidget::semanticRehighlight()
1896 m_semanticHighlighter->rehighlight(currentSource());
1899 void CPPEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo)
1901 if (semanticInfo.revision != editorRevision()) {
1902 // got outdated semantic info
1903 semanticRehighlight();
1907 const SemanticInfo previousSemanticInfo = m_lastSemanticInfo;
1908 m_lastSemanticInfo = semanticInfo; // update the semantic info
1910 int line = 0, column = 0;
1911 convertPosition(position(), &line, &column);
1913 QList<QTextEdit::ExtraSelection> unusedSelections;
1915 m_renameSelections.clear();
1916 m_currentRenameSelection = NoCurrentRenameSelection;
1918 SemanticInfo::LocalUseIterator it(semanticInfo.localUses);
1919 while (it.hasNext()) {
1921 const QList<SemanticInfo::Use> &uses = it.value();
1924 foreach (const SemanticInfo::Use &use, uses) {
1926 unsigned c = column + 1; // convertCursorPosition() returns a 0-based column number.
1927 if (l == use.line && c >= use.column && c <= (use.column + use.length)) {
1933 if (uses.size() == 1)
1934 // it's an unused declaration
1935 highlightUses(uses, semanticInfo, &unusedSelections);
1937 else if (good && m_renameSelections.isEmpty())
1938 highlightUses(uses, semanticInfo, &m_renameSelections);
1941 if (m_lastSemanticInfo.forced || previousSemanticInfo.revision != semanticInfo.revision) {
1942 QTextCharFormat diagnosticMessageFormat;
1943 diagnosticMessageFormat.setUnderlineColor(Qt::darkYellow); // ### hardcoded
1944 diagnosticMessageFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); // ### hardcoded
1946 setExtraSelections(UndefinedSymbolSelection, createSelections(document(),
1947 semanticInfo.diagnosticMessages,
1948 diagnosticMessageFormat));
1950 m_highlighter.cancel();
1952 if (! semanticHighlighterDisabled && semanticInfo.doc) {
1953 if (Core::EditorManager::instance()->currentEditor() == editor()) {
1954 LookupContext context(semanticInfo.doc, semanticInfo.snapshot);
1955 CheckSymbols::Future f = CheckSymbols::go(semanticInfo.doc, context);
1957 m_highlightRevision = semanticInfo.revision;
1958 m_nextHighlightBlockNumber = 0;
1959 m_highlightWatcher.setFuture(m_highlighter);
1963 #if 0 // ### TODO: enable objc semantic highlighting
1964 setExtraSelections(ObjCSelection, createSelections(document(),
1965 semanticInfo.objcKeywords,
1971 setExtraSelections(UnusedSymbolSelection, unusedSelections);
1973 if (! m_renameSelections.isEmpty())
1974 setExtraSelections(CodeSemanticsSelection, m_renameSelections); // ###
1976 markSymbols(textCursor(), semanticInfo);
1979 m_lastSemanticInfo.forced = false; // clear the forced flag
1984 class FindObjCKeywords: public ASTVisitor
1987 FindObjCKeywords(TranslationUnit *unit)
1991 QList<SemanticInfo::Use> operator()()
1994 accept(translationUnit()->ast());
1998 virtual bool visit(ObjCClassDeclarationAST *ast)
2000 addToken(ast->interface_token);
2001 addToken(ast->implementation_token);
2002 addToken(ast->end_token);
2006 virtual bool visit(ObjCClassForwardDeclarationAST *ast)
2007 { addToken(ast->class_token); return true; }
2009 virtual bool visit(ObjCProtocolDeclarationAST *ast)
2010 { addToken(ast->protocol_token); addToken(ast->end_token); return true; }
2012 virtual bool visit(ObjCProtocolForwardDeclarationAST *ast)
2013 { addToken(ast->protocol_token); return true; }
2015 virtual bool visit(ObjCProtocolExpressionAST *ast)
2016 { addToken(ast->protocol_token); return true; }
2018 virtual bool visit(ObjCTypeNameAST *) { return true; }
2020 virtual bool visit(ObjCEncodeExpressionAST *ast)
2021 { addToken(ast->encode_token); return true; }
2023 virtual bool visit(ObjCSelectorExpressionAST *ast)
2024 { addToken(ast->selector_token); return true; }
2026 virtual bool visit(ObjCVisibilityDeclarationAST *ast)
2027 { addToken(ast->visibility_token); return true; }
2029 virtual bool visit(ObjCPropertyAttributeAST *ast)
2031 const Identifier *attrId = identifier(ast->attribute_identifier_token);
2032 if (attrId == control()->objcAssignId()
2033 || attrId == control()->objcCopyId()
2034 || attrId == control()->objcGetterId()
2035 || attrId == control()->objcNonatomicId()
2036 || attrId == control()->objcReadonlyId()
2037 || attrId == control()->objcReadwriteId()
2038 || attrId == control()->objcRetainId()
2039 || attrId == control()->objcSetterId())
2040 addToken(ast->attribute_identifier_token);
2044 virtual bool visit(ObjCPropertyDeclarationAST *ast)
2045 { addToken(ast->property_token); return true; }
2047 virtual bool visit(ObjCSynthesizedPropertiesDeclarationAST *ast)
2048 { addToken(ast->synthesized_token); return true; }
2050 virtual bool visit(ObjCDynamicPropertiesDeclarationAST *ast)
2051 { addToken(ast->dynamic_token); return true; }
2053 virtual bool visit(ObjCFastEnumerationAST *ast)
2054 { addToken(ast->for_token); addToken(ast->in_token); return true; }
2056 virtual bool visit(ObjCSynchronizedStatementAST *ast)
2057 { addToken(ast->synchronized_token); return true; }
2060 void addToken(unsigned token)
2063 SemanticInfo::Use use;
2064 getTokenStartPosition(token, &use.line, &use.column);
2065 use.length = tokenAt(token).length();
2066 _keywords.append(use);
2071 QList<SemanticInfo::Use> _keywords;
2074 } // anonymous namespace
2076 SemanticHighlighter::Source CPPEditorWidget::currentSource(bool force)
2078 int line = 0, column = 0;
2079 convertPosition(position(), &line, &column);
2081 const Snapshot snapshot = m_modelManager->snapshot();
2082 const QString fileName = file()->fileName();
2085 if (force || m_lastSemanticInfo.revision != editorRevision())
2086 code = toPlainText(); // get the source code only when needed.
2088 const unsigned revision = editorRevision();
2089 SemanticHighlighter::Source source(snapshot, fileName, code,
2090 line, column, revision);
2091 source.force = force;
2095 SemanticHighlighter::SemanticHighlighter(QObject *parent)
2101 SemanticHighlighter::~SemanticHighlighter()
2105 void SemanticHighlighter::abort()
2107 QMutexLocker locker(&m_mutex);
2109 m_condition.wakeOne();
2112 void SemanticHighlighter::rehighlight(const Source &source)
2114 QMutexLocker locker(&m_mutex);
2116 m_condition.wakeOne();
2119 bool SemanticHighlighter::isOutdated()
2121 QMutexLocker locker(&m_mutex);
2122 const bool outdated = ! m_source.fileName.isEmpty() || m_done;
2126 void SemanticHighlighter::run()
2128 setPriority(QThread::LowestPriority);
2133 while (! (m_done || ! m_source.fileName.isEmpty()))
2134 m_condition.wait(&m_mutex);
2136 const bool done = m_done;
2137 const Source source = m_source;
2145 const SemanticInfo info = semanticInfo(source);
2147 if (! isOutdated()) {
2149 m_lastSemanticInfo = info;
2157 SemanticInfo SemanticHighlighter::semanticInfo(const Source &source)
2160 const int revision = m_lastSemanticInfo.revision;
2165 QList<Document::DiagnosticMessage> diagnosticMessages;
2166 QList<SemanticInfo::Use> objcKeywords;
2168 if (! source.force && revision == source.revision) {
2170 snapshot = m_lastSemanticInfo.snapshot; // ### TODO: use the new snapshot.
2171 doc = m_lastSemanticInfo.doc;
2172 diagnosticMessages = m_lastSemanticInfo.diagnosticMessages;
2173 objcKeywords = m_lastSemanticInfo.objcKeywords;
2178 snapshot = source.snapshot;
2179 const QByteArray preprocessedCode = snapshot.preprocessedCode(source.code, source.fileName);
2181 doc = snapshot.documentFromSource(preprocessedCode, source.fileName);
2182 doc->control()->setTopLevelDeclarationProcessor(this);
2186 if (TranslationUnit *unit = doc->translationUnit()) {
2187 FindObjCKeywords findObjCKeywords(unit); // ### remove me
2188 objcKeywords = findObjCKeywords();
2193 TranslationUnit *translationUnit = doc->translationUnit();
2194 AST *ast = translationUnit->ast();
2196 FunctionDefinitionUnderCursor functionDefinitionUnderCursor(translationUnit);
2197 DeclarationAST *currentFunctionDefinition = functionDefinitionUnderCursor(ast, source.line, source.column);
2199 const LocalSymbols useTable(doc, currentFunctionDefinition);
2201 SemanticInfo semanticInfo;
2202 semanticInfo.revision = source.revision;
2203 semanticInfo.snapshot = snapshot;
2204 semanticInfo.doc = doc;
2205 semanticInfo.localUses = useTable.uses;
2206 semanticInfo.hasQ = useTable.hasQ;
2207 semanticInfo.hasD = useTable.hasD;
2208 semanticInfo.forced = source.force;
2209 semanticInfo.diagnosticMessages = diagnosticMessages;
2210 semanticInfo.objcKeywords = objcKeywords;
2212 return semanticInfo;
2215 QModelIndex CPPEditorWidget::indexForPosition(int line, int column, const QModelIndex &rootIndex) const
2217 QModelIndex lastIndex = rootIndex;
2219 const int rowCount = m_outlineModel->rowCount(rootIndex);
2220 for (int row = 0; row < rowCount; ++row) {
2221 const QModelIndex index = m_outlineModel->index(row, 0, rootIndex);
2222 Symbol *symbol = m_outlineModel->symbolFromIndex(index);
2223 if (symbol && symbol->line() > unsigned(line))
2228 if (lastIndex != rootIndex) {
2230 lastIndex = indexForPosition(line, column, lastIndex);
2236 QVector<QString> CPPEditorWidget::highlighterFormatCategories()
2238 static QVector<QString> categories;
2239 if (categories.isEmpty()) {
2240 categories << QLatin1String(TextEditor::Constants::C_NUMBER)
2241 << QLatin1String(TextEditor::Constants::C_STRING)
2242 << QLatin1String(TextEditor::Constants::C_TYPE)
2243 << QLatin1String(TextEditor::Constants::C_KEYWORD)
2244 << QLatin1String(TextEditor::Constants::C_OPERATOR)
2245 << QLatin1String(TextEditor::Constants::C_PREPROCESSOR)
2246 << QLatin1String(TextEditor::Constants::C_LABEL)
2247 << QLatin1String(TextEditor::Constants::C_COMMENT)
2248 << QLatin1String(TextEditor::Constants::C_DOXYGEN_COMMENT)
2249 << QLatin1String(TextEditor::Constants::C_DOXYGEN_TAG)
2250 << QLatin1String(TextEditor::Constants::C_VISUAL_WHITESPACE);
2255 #include "cppeditor.moc"