/*************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Qt Software Information (qt-info@nokia.com) ** ** ** Non-Open Source Usage ** ** Licensees may use this file in accordance with the Qt Beta Version ** License Agreement, Agreement version 2.2 provided with the Software or, ** alternatively, in accordance with the terms contained in a written ** agreement between you and Nokia. ** ** GNU General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU General ** Public License versions 2.0 or 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the packaging ** of this file. Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and ** http://www.gnu.org/copyleft/gpl.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt GPL Exception ** version 1.3, included in the file GPL_EXCEPTION.txt in this package. ** ***************************************************************************/ #include "cppeditor.h" #include "cppeditorconstants.h" #include "cppplugin.h" #include "cpphighlighter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace CPlusPlus; using namespace CppEditor::Internal; namespace { class OverviewTreeView : public QTreeView { public: OverviewTreeView(QWidget *parent = 0) : QTreeView(parent) { // TODO: Disable the root for all items (with a custom delegate?) setRootIsDecorated(false); } void sync() { expandAll(); setMinimumWidth(qMax(sizeHintForColumn(0), minimumSizeHint().width())); } }; } // end of anonymous namespace QualifiedNameId *qualifiedNameIdForSymbol(Symbol *s, const LookupContext &context) { Name *symbolName = s->name(); if (! symbolName) return 0; // nothing to do. QVector names; for (Scope *scope = s->scope(); scope; scope = scope->enclosingScope()) { if (scope->isClassScope() || scope->isNamespaceScope()) { if (scope->owner() && scope->owner()->name()) { Name *ownerName = scope->owner()->name(); if (QualifiedNameId *q = ownerName->asQualifiedNameId()) { for (unsigned i = 0; i < q->nameCount(); ++i) { names.prepend(q->nameAt(i)); } } else { names.prepend(ownerName); } } } } if (QualifiedNameId *q = symbolName->asQualifiedNameId()) { for (unsigned i = 0; i < q->nameCount(); ++i) { names.append(q->nameAt(i)); } } else { names.append(symbolName); } return context.control()->qualifiedNameId(names.constData(), names.size()); } CPPEditorEditable::CPPEditorEditable(CPPEditor *editor) : BaseTextEditorEditable(editor) { Core::UniqueIDManager *uidm = Core::UniqueIDManager::instance(); m_context << uidm->uniqueIdentifier(CppEditor::Constants::C_CPPEDITOR); m_context << uidm->uniqueIdentifier(ProjectExplorer::Constants::LANG_CXX); m_context << uidm->uniqueIdentifier(TextEditor::Constants::C_TEXTEDITOR); } CPPEditor::CPPEditor(QWidget *parent) : TextEditor::BaseTextEditor(parent) { setParenthesesMatchingEnabled(true); setMarksVisible(true); setCodeFoldingVisible(true); baseTextDocument()->setSyntaxHighlighter(new CppHighlighter); // new QShortcut(QKeySequence("Ctrl+Alt+M"), this, SLOT(foo()), 0, Qt::WidgetShortcut); #ifdef WITH_TOKEN_MOVE_POSITION new QShortcut(QKeySequence::MoveToPreviousWord, this, SLOT(moveToPreviousToken()), /*ambiguousMember=*/ 0, Qt::WidgetShortcut); new QShortcut(QKeySequence::MoveToNextWord, this, SLOT(moveToNextToken()), /*ambiguousMember=*/ 0, Qt::WidgetShortcut); new QShortcut(QKeySequence::DeleteStartOfWord, this, SLOT(deleteStartOfToken()), /*ambiguousMember=*/ 0, Qt::WidgetShortcut); new QShortcut(QKeySequence::DeleteEndOfWord, this, SLOT(deleteEndOfToken()), /*ambiguousMember=*/ 0, Qt::WidgetShortcut); #endif m_modelManager = ExtensionSystem::PluginManager::instance() ->getObject(); if (m_modelManager) { connect(m_modelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)), this, SLOT(onDocumentUpdated(CPlusPlus::Document::Ptr))); } } CPPEditor::~CPPEditor() { } TextEditor::BaseTextEditorEditable *CPPEditor::createEditableInterface() { CPPEditorEditable *editable = new CPPEditorEditable(this); createToolBar(editable); return editable; } void CPPEditor::createToolBar(CPPEditorEditable *editable) { m_methodCombo = new QComboBox; m_methodCombo->setMinimumContentsLength(22); m_methodCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); // Make the combo box prefer to expand QSizePolicy policy = m_methodCombo->sizePolicy(); policy.setHorizontalPolicy(QSizePolicy::Expanding); m_methodCombo->setSizePolicy(policy); QTreeView *methodView = new OverviewTreeView(); methodView->header()->hide(); methodView->setItemsExpandable(false); m_methodCombo->setView(methodView); m_methodCombo->setMaxVisibleItems(20); m_overviewModel = new OverviewModel(this); m_methodCombo->setModel(m_overviewModel); connect(m_methodCombo, SIGNAL(activated(int)), this, SLOT(jumpToMethod(int))); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateMethodBoxIndex())); connect(m_methodCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateMethodBoxToolTip())); connect(file(), SIGNAL(changed()), this, SLOT(updateFileName())); QToolBar *toolBar = editable->toolBar(); QList actions = toolBar->actions(); toolBar->insertWidget(actions.first(), m_methodCombo); } int CPPEditor::previousBlockState(QTextBlock block) const { block = block.previous(); if (block.isValid()) { int state = block.userState(); if (state != -1) return state; } return 0; } QTextCursor CPPEditor::moveToPreviousToken(QTextCursor::MoveMode mode) const { SimpleLexer tokenize; QTextCursor c(textCursor()); QTextBlock block = c.block(); int column = c.columnNumber(); for (; block.isValid(); block = block.previous()) { const QString textBlock = block.text(); QList tokens = tokenize(textBlock, previousBlockState(block)); if (! tokens.isEmpty()) { tokens.prepend(SimpleToken()); for (int index = tokens.size() - 1; index != -1; --index) { const SimpleToken &tk = tokens.at(index); if (tk.position() < column) { c.setPosition(block.position() + tk.position(), mode); return c; } } } column = INT_MAX; } c.movePosition(QTextCursor::Start, mode); return c; } QTextCursor CPPEditor::moveToNextToken(QTextCursor::MoveMode mode) const { SimpleLexer tokenize; QTextCursor c(textCursor()); QTextBlock block = c.block(); int column = c.columnNumber(); for (; block.isValid(); block = block.next()) { const QString textBlock = block.text(); QList tokens = tokenize(textBlock, previousBlockState(block)); if (! tokens.isEmpty()) { for (int index = 0; index < tokens.size(); ++index) { const SimpleToken &tk = tokens.at(index); if (tk.position() > column) { c.setPosition(block.position() + tk.position(), mode); return c; } } } column = -1; } c.movePosition(QTextCursor::End, mode); return c; } void CPPEditor::moveToPreviousToken() { setTextCursor(moveToPreviousToken(QTextCursor::MoveAnchor)); } void CPPEditor::moveToNextToken() { setTextCursor(moveToNextToken(QTextCursor::MoveAnchor)); } void CPPEditor::deleteStartOfToken() { QTextCursor c = moveToPreviousToken(QTextCursor::KeepAnchor); c.removeSelectedText(); setTextCursor(c); } void CPPEditor::deleteEndOfToken() { QTextCursor c = moveToNextToken(QTextCursor::KeepAnchor); c.removeSelectedText(); setTextCursor(c); } void CPPEditor::onDocumentUpdated(Document::Ptr doc) { if (doc->fileName() != file()->fileName()) return; m_overviewModel->rebuild(doc); OverviewTreeView *treeView = static_cast(m_methodCombo->view()); treeView->sync(); updateMethodBoxIndex(); } void CPPEditor::updateFileName() { } void CPPEditor::jumpToMethod(int) { QModelIndex index = m_methodCombo->view()->currentIndex(); Symbol *symbol = m_overviewModel->symbolFromIndex(index); if (! symbol) return; Core::EditorManager::instance()->addCurrentPositionToNavigationHistory(true); int line = symbol->line(); gotoLine(line); Core::EditorManager::instance()->addCurrentPositionToNavigationHistory(); setFocus(); } void CPPEditor::updateMethodBoxIndex() { int line = 0, column = 0; convertPosition(position(), &line, &column); QModelIndex lastIndex; const int rc = m_overviewModel->rowCount(); for (int row = 0; row < rc; ++row) { const QModelIndex index = m_overviewModel->index(row, 0, QModelIndex()); Symbol *symbol = m_overviewModel->symbolFromIndex(index); if (symbol && symbol->line() > unsigned(line)) break; lastIndex = index; } if (lastIndex.isValid()) { bool blocked = m_methodCombo->blockSignals(true); m_methodCombo->setCurrentIndex(lastIndex.row()); updateMethodBoxToolTip(); (void) m_methodCombo->blockSignals(blocked); } } void CPPEditor::updateMethodBoxToolTip() { m_methodCombo->setToolTip(m_methodCombo->currentText()); } static bool isCompatible(Name *name, Name *otherName) { if (NameId *nameId = name->asNameId()) { if (TemplateNameId *otherTemplId = otherName->asTemplateNameId()) return nameId->identifier()->isEqualTo(otherTemplId->identifier()); } else if (TemplateNameId *templId = name->asTemplateNameId()) { if (NameId *otherNameId = otherName->asNameId()) return templId->identifier()->isEqualTo(otherNameId->identifier()); } return name->isEqualTo(otherName); } static bool isCompatible(Function *definition, Symbol *declaration, QualifiedNameId *declarationName) { Function *declTy = declaration->type()->asFunction(); if (! declTy) return false; Name *definitionName = definition->name(); if (QualifiedNameId *q = definitionName->asQualifiedNameId()) { if (! isCompatible(q->unqualifiedNameId(), declaration->name())) return false; else if (q->nameCount() > declarationName->nameCount()) return false; else if (declTy->argumentCount() != definition->argumentCount()) return false; else if (declTy->isConst() != definition->isConst()) return false; else if (declTy->isVolatile() != definition->isVolatile()) return false; for (unsigned i = 0; i < definition->argumentCount(); ++i) { Symbol *arg = definition->argumentAt(i); Symbol *otherArg = declTy->argumentAt(i); if (! arg->type().isEqualTo(otherArg->type())) return false; } for (unsigned i = 0; i != q->nameCount(); ++i) { Name *n = q->nameAt(q->nameCount() - i - 1); Name *m = declarationName->nameAt(declarationName->nameCount() - i - 1); if (! isCompatible(n, m)) return false; } return true; } else { // ### TODO: implement isCompatible for unqualified name ids. } return false; } void CPPEditor::switchDeclarationDefinition() { int line = 0, column = 0; convertPosition(position(), &line, &column); if (!m_modelManager) return; const Snapshot snapshot = m_modelManager->snapshot(); Document::Ptr doc = snapshot.value(file()->fileName()); if (!doc) return; Symbol *lastSymbol = doc->findSymbolAt(line, column); if (!lastSymbol || !lastSymbol->scope()) return; Function *f = lastSymbol->asFunction(); if (!f) { Scope *fs = lastSymbol->scope(); if (!fs->isFunctionScope()) fs = fs->enclosingFunctionScope(); if (fs) f = fs->owner()->asFunction(); } if (f) { TypeOfExpression typeOfExpression; typeOfExpression.setSnapshot(m_modelManager->snapshot()); QList resolvedSymbols = typeOfExpression(QString(), doc, lastSymbol); const LookupContext &context = typeOfExpression.lookupContext(); QualifiedNameId *q = qualifiedNameIdForSymbol(f, context); QList symbols = context.resolve(q); Symbol *declaration = 0; foreach (declaration, symbols) { if (isCompatible(f, declaration, q)) break; } if (! declaration && ! symbols.isEmpty()) declaration = symbols.first(); if (declaration) openEditorAt(declaration); } else if (lastSymbol->type()->isFunction()) { if (Symbol *def = findDefinition(lastSymbol)) openEditorAt(def); } } void CPPEditor::jumpToDefinition() { if (!m_modelManager) return; const Snapshot snapshot = m_modelManager->snapshot(); // Find the last symbol up to the cursor position int line = 0, column = 0; convertPosition(position(), &line, &column); Document::Ptr doc = snapshot.value(file()->fileName()); if (!doc) return; QTextCursor tc = textCursor(); unsigned lineno = tc.blockNumber() + 1; foreach (const Document::Include &incl, doc->includes()) { if (incl.line() == lineno) { if (openCppEditorAt(incl.fileName(), 0, 0)) return; // done break; } } Symbol *lastSymbol = doc->findSymbolAt(line, column); if (!lastSymbol) return; // Get the expression under the cursor const int endOfName = endOfNameUnderCursor(); tc.setPosition(endOfName); ExpressionUnderCursor expressionUnderCursor; const QString expression = expressionUnderCursor(tc); // Evaluate the type of the expression TypeOfExpression typeOfExpression; typeOfExpression.setSnapshot(m_modelManager->snapshot()); QList resolvedSymbols = typeOfExpression(expression, doc, lastSymbol); if (!resolvedSymbols.isEmpty()) { Symbol *symbol = resolvedSymbols.first().second; if (symbol) { Symbol *def = 0; if (!lastSymbol->isFunction()) def = findDefinition(symbol); if (def) openEditorAt(def); else openEditorAt(symbol); // This would jump to the type of a name #if 0 } else if (NamedType *namedType = firstType->asNamedType()) { QList candidates = context.resolve(namedType->name()); if (!candidates.isEmpty()) { Symbol *s = candidates.takeFirst(); openCppEditorAt(s->fileName(), s->line(), s->column()); } #endif } } else { foreach (const Document::MacroUse use, doc->macroUses()) { if (use.contains(endOfName - 1)) { const Macro ¯o = use.macro(); const QString fileName = QString::fromUtf8(macro.fileName()); if (openCppEditorAt(fileName, macro.line(), 0)) return; // done } } qDebug() << "No results for expression:" << expression; } } Symbol *CPPEditor::findDefinition(Symbol *lastSymbol) { // Currently only functions are supported if (!lastSymbol->type()->isFunction()) return 0; QVector qualifiedName; Scope *scope = lastSymbol->scope(); for (; scope; scope = scope->enclosingScope()) { if (scope->isClassScope() || scope->isNamespaceScope()) { if (scope->owner() && scope->owner()->name()) { Name *scopeOwnerName = scope->owner()->name(); if (QualifiedNameId *q = scopeOwnerName->asQualifiedNameId()) { for (unsigned i = 0; i < q->nameCount(); ++i) { qualifiedName.prepend(q->nameAt(i)); } } else { qualifiedName.prepend(scopeOwnerName); } } } } qualifiedName.append(lastSymbol->name()); Control control; QualifiedNameId *q = control.qualifiedNameId(&qualifiedName[0], qualifiedName.size()); LookupContext context(&control); const Snapshot documents = m_modelManager->snapshot(); foreach (Document::Ptr doc, documents) { QList visibleScopes; visibleScopes.append(doc->globalSymbols()); visibleScopes = context.expand(visibleScopes); //qDebug() << "** doc:" << doc->fileName() << "visible scopes:" << visibleScopes.count(); foreach (Scope *visibleScope, visibleScopes) { Symbol *symbol = 0; if (NameId *nameId = q->unqualifiedNameId()->asNameId()) symbol = visibleScope->lookat(nameId->identifier()); else if (DestructorNameId *dtorId = q->unqualifiedNameId()->asDestructorNameId()) symbol = visibleScope->lookat(dtorId->identifier()); else if (TemplateNameId *templNameId = q->unqualifiedNameId()->asTemplateNameId()) symbol = visibleScope->lookat(templNameId->identifier()); else if (OperatorNameId *opId = q->unqualifiedNameId()->asOperatorNameId()) symbol = visibleScope->lookat(opId->kind()); // ### cast operators for (; symbol; symbol = symbol->next()) { if (! symbol->isFunction()) continue; else if (! isCompatible(symbol->asFunction(), lastSymbol, q)) continue; return symbol; } } } return 0; } bool CPPEditor::isElectricCharacter(const QChar &ch) const { if (ch == QLatin1Char('{') || ch == QLatin1Char('}') || ch == QLatin1Char('#')) { return true; } return false; } // Indent a code line based on previous template static void indentCPPBlock(const CPPEditor::TabSettings &ts, const QTextBlock &block, const Iterator &programBegin, const Iterator &programEnd, QChar typedChar) { typedef typename SharedTools::Indenter Indenter; Indenter &indenter = Indenter::instance(); indenter.setIndentSize(ts.m_indentSize); indenter.setTabSize(ts.m_tabSize); const TextEditor::TextBlockIterator current(block); const int indent = indenter.indentForBottomLine(current, programBegin, programEnd, typedChar); ts.indentLine(block, indent); } void CPPEditor::indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar) { const TextEditor::TextBlockIterator begin(doc->begin()); const TextEditor::TextBlockIterator end(block.next()); indentCPPBlock(tabSettings(), block, begin, end, typedChar); } void CPPEditor::contextMenuEvent(QContextMenuEvent *e) { QMenu *menu = createStandardContextMenu(); // Remove insert unicode control character QAction *lastAction = menu->actions().last(); if (lastAction->menu() && QLatin1String(lastAction->menu()->metaObject()->className()) == QLatin1String("QUnicodeControlCharacterMenu")) menu->removeAction(lastAction); Core::ActionContainer *mcontext = Core::ICore::instance()->actionManager()->actionContainer(CppEditor::Constants::M_CONTEXT); QMenu *contextMenu = mcontext->menu(); foreach (QAction *action, contextMenu->actions()) menu->addAction(action); menu->exec(e->globalPos()); delete menu; } QList CPPEditorEditable::context() const { return m_context; } Core::IEditor *CPPEditorEditable::duplicate(QWidget *parent) { CPPEditor *newEditor = new CPPEditor(parent); newEditor->duplicateFrom(editor()); CppPlugin::instance()->initializeEditor(newEditor); return newEditor->editableInterface(); } const char *CPPEditorEditable::kind() const { return CppEditor::Constants::CPPEDITOR_KIND; } void CPPEditor::setFontSettings(const TextEditor::FontSettings &fs) { TextEditor::BaseTextEditor::setFontSettings(fs); CppHighlighter *highlighter = qobject_cast(baseTextDocument()->syntaxHighlighter()); if (!highlighter) return; static QVector categories; if (categories.isEmpty()) { categories << QLatin1String(TextEditor::Constants::C_NUMBER) << QLatin1String(TextEditor::Constants::C_STRING) << QLatin1String(TextEditor::Constants::C_TYPE) << QLatin1String(TextEditor::Constants::C_KEYWORD) << QLatin1String(TextEditor::Constants::C_OPERATOR) << QLatin1String(TextEditor::Constants::C_PREPROCESSOR) << QLatin1String(TextEditor::Constants::C_LABEL) << QLatin1String(TextEditor::Constants::C_COMMENT); } const QVector formats = fs.toTextCharFormats(categories); highlighter->setFormats(formats.constBegin(), formats.constEnd()); highlighter->rehighlight(); } void CPPEditor::unCommentSelection() { QTextCursor cursor = textCursor(); cursor.beginEditBlock(); int pos = cursor.position(); int anchor = cursor.anchor(); int start = qMin(anchor, pos); int end = qMax(anchor, pos); bool anchorIsStart = (anchor == start); QTextBlock startBlock = document()->findBlock(start); QTextBlock endBlock = document()->findBlock(end); if (end > start && endBlock.position() == end) { --end; endBlock = endBlock.previous(); } bool doCStyleUncomment = false; bool doCStyleComment = false; bool doCppStyleUncomment = false; bool hasSelection = cursor.hasSelection(); if (hasSelection) { QString startText = startBlock.text(); int startPos = start - startBlock.position(); bool hasLeadingCharacters = !startText.left(startPos).trimmed().isEmpty(); if ((startPos >= 2 && startText.at(startPos-2) == QLatin1Char('/') && startText.at(startPos-1) == QLatin1Char('*'))) { startPos -= 2; start -= 2; } bool hasSelStart = (startPos < startText.length() - 2 && startText.at(startPos) == QLatin1Char('/') && startText.at(startPos+1) == QLatin1Char('*')); QString endText = endBlock.text(); int endPos = end - endBlock.position(); bool hasTrailingCharacters = !endText.left(endPos).remove(QLatin1String("//")).trimmed().isEmpty() && !endText.mid(endPos).trimmed().isEmpty(); if ((endPos <= endText.length() - 2 && endText.at(endPos) == QLatin1Char('*') && endText.at(endPos+1) == QLatin1Char('/'))) { endPos += 2; end += 2; } bool hasSelEnd = (endPos >= 2 && endText.at(endPos-2) == QLatin1Char('*') && endText.at(endPos-1) == QLatin1Char('/')); doCStyleUncomment = hasSelStart && hasSelEnd; doCStyleComment = !doCStyleUncomment && (hasLeadingCharacters || hasTrailingCharacters); } if (doCStyleUncomment) { cursor.setPosition(end); cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, 2); cursor.removeSelectedText(); cursor.setPosition(start); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2); cursor.removeSelectedText(); } else if (doCStyleComment) { cursor.setPosition(end); cursor.insertText(QLatin1String("*/")); cursor.setPosition(start); cursor.insertText(QLatin1String("/*")); } else { endBlock = endBlock.next(); doCppStyleUncomment = true; for (QTextBlock block = startBlock; block != endBlock; block = block.next()) { QString text = block.text(); if (!text.trimmed().startsWith(QLatin1String("//"))) { doCppStyleUncomment = false; break; } } for (QTextBlock block = startBlock; block != endBlock; block = block.next()) { if (doCppStyleUncomment) { QString text = block.text(); int i = 0; while (i < text.size() - 1) { if (text.at(i) == QLatin1Char('/') && text.at(i + 1) == QLatin1Char('/')) { cursor.setPosition(block.position() + i); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2); cursor.removeSelectedText(); break; } if (!text.at(i).isSpace()) break; ++i; } } else { cursor.setPosition(block.position()); cursor.insertText(QLatin1String("//")); } } } // adjust selection when commenting out if (hasSelection && !doCStyleUncomment && !doCppStyleUncomment) { cursor = textCursor(); if (!doCStyleComment) start = startBlock.position(); // move the double slashes into the selection int lastSelPos = anchorIsStart ? cursor.position() : cursor.anchor(); if (anchorIsStart) { cursor.setPosition(start); cursor.setPosition(lastSelPos, QTextCursor::KeepAnchor); } else { cursor.setPosition(lastSelPos); cursor.setPosition(start, QTextCursor::KeepAnchor); } setTextCursor(cursor); } cursor.endEditBlock(); } int CPPEditor::endOfNameUnderCursor() { int pos = position(); QChar chr = characterAt(pos); // Skip to the start of a name while (chr.isLetterOrNumber() || chr == QLatin1Char('_')) chr = characterAt(++pos); return pos; } TextEditor::ITextEditor *CPPEditor::openCppEditorAt(const QString &fileName, int line, int column) { return TextEditor::BaseTextEditor::openEditorAt(fileName, line, column, Constants::C_CPPEDITOR); } bool CPPEditor::openEditorAt(Symbol *s) { const QString fileName = QString::fromUtf8(s->fileName(), s->fileNameLength()); return openCppEditorAt(fileName, s->line(), s->column()); }