/**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "cppuseselectionsupdater.h" #include "cppcanonicalsymbol.h" #include "cppeditor.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace CPlusPlus; enum { updateUseSelectionsInternalInMs = 500 }; namespace { class FunctionDefinitionUnderCursor: protected ASTVisitor { unsigned _line; unsigned _column; DeclarationAST *_functionDefinition; public: FunctionDefinitionUnderCursor(TranslationUnit *translationUnit) : ASTVisitor(translationUnit), _line(0), _column(0) { } DeclarationAST *operator()(AST *ast, unsigned line, unsigned column) { _functionDefinition = 0; _line = line; _column = column; accept(ast); return _functionDefinition; } protected: virtual bool preVisit(AST *ast) { if (_functionDefinition) return false; if (FunctionDefinitionAST *def = ast->asFunctionDefinition()) return checkDeclaration(def); if (ObjCMethodDeclarationAST *method = ast->asObjCMethodDeclaration()) { if (method->function_body) return checkDeclaration(method); } return true; } private: bool checkDeclaration(DeclarationAST *ast) { unsigned startLine, startColumn; unsigned endLine, endColumn; getTokenStartPosition(ast->firstToken(), &startLine, &startColumn); getTokenEndPosition(ast->lastToken() - 1, &endLine, &endColumn); if (_line > startLine || (_line == startLine && _column >= startColumn)) { if (_line < endLine || (_line == endLine && _column < endColumn)) { _functionDefinition = ast; return false; } } return true; } }; QTextEdit::ExtraSelection extraSelection(const QTextCharFormat &format, const QTextCursor &cursor) { QTextEdit::ExtraSelection selection; selection.format = format; selection.cursor = cursor; return selection; } struct Params { // Shared Document::Ptr document; // For local use calculation int line; int column; // For references calculation Scope *scope; QString expression; Snapshot snapshot; }; using CppEditor::Internal::SemanticUses; void splitLocalUses(const CppTools::SemanticInfo::LocalUseMap &uses, const Params &p, SemanticUses *selectionsForLocalVariableUnderCursor, SemanticUses *selectionsForLocalUnusedVariables) { QTC_ASSERT(selectionsForLocalVariableUnderCursor, return); QTC_ASSERT(selectionsForLocalUnusedVariables, return); LookupContext context(p.document, p.snapshot); CppTools::SemanticInfo::LocalUseIterator it(uses); while (it.hasNext()) { it.next(); const SemanticUses &uses = it.value(); bool good = false; foreach (const CppTools::SemanticInfo::Use &use, uses) { unsigned l = p.line; unsigned c = p.column + 1; // convertCursorPosition() returns a 0-based column number. if (l == use.line && c >= use.column && c <= (use.column + use.length)) { good = true; break; } } if (uses.size() == 1) { if (!CppTools::isOwnershipRAIIType(it.key(), context)) selectionsForLocalUnusedVariables->append(uses); // unused declaration } else if (good && selectionsForLocalVariableUnderCursor->isEmpty()) { selectionsForLocalVariableUnderCursor->append(uses); } } } CppTools::SemanticInfo::LocalUseMap findLocalUses(const Params &p) { AST *ast = p.document->translationUnit()->ast(); FunctionDefinitionUnderCursor functionDefinitionUnderCursor(p.document->translationUnit()); DeclarationAST *declaration = functionDefinitionUnderCursor(ast, p.line, p.column); return CppTools::LocalSymbols(p.document, declaration).uses; } QList findReferences(const Params &p) { QList result; if (!p.scope || p.expression.isEmpty()) return result; TypeOfExpression typeOfExpression; Snapshot snapshot = p.snapshot; snapshot.insert(p.document); typeOfExpression.init(p.document, snapshot); typeOfExpression.setExpandTemplates(true); using CppEditor::Internal::CanonicalSymbol; if (Symbol *s = CanonicalSymbol::canonicalSymbol(p.scope, p.expression, typeOfExpression)) { CppTools::CppModelManagerInterface *mmi = CppTools::CppModelManagerInterface::instance(); result = mmi->references(s, typeOfExpression.context()); } return result; } CppEditor::Internal::UseSelectionsResult findUses(const Params p) { CppEditor::Internal::UseSelectionsResult result; const CppTools::SemanticInfo::LocalUseMap localUses = findLocalUses(p); result.localUses = localUses; splitLocalUses(localUses, p, &result.selectionsForLocalVariableUnderCursor, &result.selectionsForLocalUnusedVariables); if (!result.selectionsForLocalVariableUnderCursor.isEmpty()) return result; result.references = findReferences(p); return result; // OK, result.selectionsForLocalUnusedVariables will be passed on } } // anonymous namespace namespace CppEditor { namespace Internal { CppUseSelectionsUpdater::CppUseSelectionsUpdater(TextEditor::BaseTextEditorWidget *editorWidget) : m_editorWidget(editorWidget) , m_findUsesRevision(-1) { m_timer.setSingleShot(true); m_timer.setInterval(updateUseSelectionsInternalInMs); connect(&m_timer, SIGNAL(timeout()), this, SLOT(update())); } void CppUseSelectionsUpdater::scheduleUpdate() { m_timer.start(); } void CppUseSelectionsUpdater::abortSchedule() { m_timer.stop(); } void CppUseSelectionsUpdater::update() { CppEditorWidget *cppEditorWidget = qobject_cast(m_editorWidget); QTC_ASSERT(cppEditorWidget, return); QTC_CHECK(cppEditorWidget->isSemanticInfoValidExceptLocalUses()); const CppTools::SemanticInfo semanticInfo = cppEditorWidget->semanticInfo(); const Document::Ptr document = semanticInfo.doc; const Snapshot snapshot = semanticInfo.snapshot; if (!document || document->editorRevision() != static_cast(textDocument()->revision())) return; QTC_ASSERT(document->translationUnit(), return); QTC_ASSERT(document->translationUnit()->ast(), return); QTC_ASSERT(!snapshot.isEmpty(), return); QTextCursor textCursor = m_editorWidget->textCursor(); if (handleMacroCase(textCursor, document)) { emit finished(CppTools::SemanticInfo::LocalUseMap()); return; } handleSymbolCase(textCursor, document, snapshot); } void CppUseSelectionsUpdater::onFindUsesFinished() { QTC_ASSERT(m_findUsesWatcher, return); if (m_findUsesWatcher->isCanceled()) return; if (m_findUsesRevision != textDocument()->revision()) return; // Optimizable: If the cursor is still on the same identifier the results are valid. if (m_findUsesCursorPosition != m_editorWidget->position()) return; const UseSelectionsResult result = m_findUsesWatcher->result(); const bool hasUsesForLocalVariable = !result.selectionsForLocalVariableUnderCursor.isEmpty(); const bool hasReferences = !result.references.isEmpty(); ExtraSelections localVariableSelections; if (hasUsesForLocalVariable) { localVariableSelections = toExtraSelections(result.selectionsForLocalVariableUnderCursor, TextEditor::C_OCCURRENCES); updateUseSelections(localVariableSelections); } else if (hasReferences) { const ExtraSelections selections = toExtraSelections(result.references, TextEditor::C_OCCURRENCES); updateUseSelections(selections); } else { if (!currentUseSelections().isEmpty()) updateUseSelections(ExtraSelections()); } updateUnusedSelections(toExtraSelections(result.selectionsForLocalUnusedVariables, TextEditor::C_OCCURRENCES_UNUSED)); m_findUsesWatcher.reset(); m_document.reset(); m_snapshot = Snapshot(); emit selectionsForVariableUnderCursorUpdated(localVariableSelections); emit finished(result.localUses); } bool CppUseSelectionsUpdater::handleMacroCase(const QTextCursor &textCursor, const Document::Ptr document) { const Macro *macro = CppTools::findCanonicalMacro(textCursor, document); if (!macro) return false; const QTextCharFormat &occurrencesFormat = textCharFormat(TextEditor::C_OCCURRENCES); ExtraSelections selections; // Macro definition if (macro->fileName() == document->fileName()) { QTextCursor cursor(textDocument()); cursor.setPosition(macro->utf16CharOffset()); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, macro->nameToQString().size()); selections.append(extraSelection(occurrencesFormat, cursor)); } // Other macro uses foreach (const Document::MacroUse &use, document->macroUses()) { const Macro &useMacro = use.macro(); if (useMacro.line() != macro->line() || useMacro.utf16CharOffset() != macro->utf16CharOffset() || useMacro.length() != macro->length() || useMacro.fileName() != macro->fileName()) continue; QTextCursor cursor(textDocument()); cursor.setPosition(use.utf16charsBegin()); cursor.setPosition(use.utf16charsEnd(), QTextCursor::KeepAnchor); selections.append(extraSelection(occurrencesFormat, cursor)); } updateUseSelections(selections); return true; } void CppUseSelectionsUpdater::handleSymbolCase(const QTextCursor &textCursor, const Document::Ptr document, const Snapshot &snapshot) { m_document = document; m_snapshot = snapshot; if (m_findUsesWatcher) m_findUsesWatcher->cancel(); m_findUsesWatcher.reset(new QFutureWatcher); connect(m_findUsesWatcher.data(), SIGNAL(finished()), this, SLOT(onFindUsesFinished())); m_findUsesRevision = textDocument()->revision(); m_findUsesCursorPosition = m_editorWidget->position(); Params params; params.document = document; m_editorWidget->convertPosition(m_findUsesCursorPosition, ¶ms.line, ¶ms.column); CanonicalSymbol canonicalSymbol(document, snapshot); params.scope = canonicalSymbol.getScopeAndExpression(textCursor, ¶ms.expression); params.snapshot = snapshot; m_findUsesWatcher->setFuture(QtConcurrent::run(&findUses, params)); } ExtraSelections CppUseSelectionsUpdater::toExtraSelections(const SemanticUses &uses, TextEditor::TextStyle style) const { ExtraSelections result; foreach (const CppTools::SemanticInfo::Use &use, uses) { if (use.isInvalid()) continue; QTextDocument *document = textDocument(); const int position = document->findBlockByNumber(use.line - 1).position() + use.column - 1; const int anchor = position + use.length; QTextEdit::ExtraSelection sel; sel.format = textCharFormat(style); sel.cursor = QTextCursor(document); sel.cursor.setPosition(anchor); sel.cursor.setPosition(position, QTextCursor::KeepAnchor); result.append(sel); } return result; } ExtraSelections CppUseSelectionsUpdater::toExtraSelections(const QList &references, TextEditor::TextStyle style) const { ExtraSelections selections; foreach (int index, references) { unsigned line, column; TranslationUnit *unit = m_document->translationUnit(); unit->getTokenPosition(index, &line, &column); if (column) --column; // adjust the column position. const int len = unit->tokenAt(index).utf16chars(); QTextCursor cursor(textDocument()->findBlockByNumber(line - 1)); cursor.setPosition(cursor.position() + column); cursor.setPosition(cursor.position() + len, QTextCursor::KeepAnchor); selections.append(extraSelection(textCharFormat(style), cursor)); } return selections; } QTextCharFormat CppUseSelectionsUpdater::textCharFormat(TextEditor::TextStyle category) const { return m_editorWidget->textDocument()->fontSettings().toTextCharFormat(category); } QTextDocument *CppUseSelectionsUpdater::textDocument() const { return m_editorWidget->document(); } ExtraSelections CppUseSelectionsUpdater::currentUseSelections() const { return m_editorWidget->extraSelections( TextEditor::BaseTextEditorWidget::CodeSemanticsSelection); } void CppUseSelectionsUpdater::updateUseSelections(const ExtraSelections &selections) { m_editorWidget->setExtraSelections(TextEditor::BaseTextEditorWidget::CodeSemanticsSelection, selections); } void CppUseSelectionsUpdater::updateUnusedSelections(const ExtraSelections &selections) { m_editorWidget->setExtraSelections(TextEditor::BaseTextEditorWidget::UnusedSymbolSelection, selections); } } // namespace Internal } // namespace CppEditor