/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** 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 The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "cppeditorwidget.h" #include "cppautocompleter.h" #include "cppdocumentationcommenthelper.h" #include "cppeditorconstants.h" #include "cppeditordocument.h" #include "cppeditorplugin.h" #include "cppfunctiondecldeflink.h" #include "cpphighlighter.h" #include "cpplocalrenaming.h" #include "cppminimizableinfobars.h" #include "cpppreprocessordialog.h" #include "cppquickfixassistant.h" #include "cppuseselectionsupdater.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL = 200 }; using namespace Core; using namespace CPlusPlus; using namespace CppTools; using namespace TextEditor; using namespace Utils; namespace CppEditor { namespace Internal { class CppEditorWidgetPrivate { public: CppEditorWidgetPrivate(CppEditorWidget *q); public: QPointer m_modelManager; CppEditorDocument *m_cppEditorDocument; CppEditorOutline *m_cppEditorOutline; QTimer m_updateFunctionDeclDefLinkTimer; SemanticInfo m_lastSemanticInfo; FunctionDeclDefLinkFinder *m_declDefLinkFinder; QSharedPointer m_declDefLink; QAction *m_parseContextAction = nullptr; ParseContextWidget *m_parseContextWidget = nullptr; QToolButton *m_preprocessorButton = nullptr; MinimizableInfoBars::Actions m_showInfoBarActions; CppLocalRenaming m_localRenaming; CppUseSelectionsUpdater m_useSelectionsUpdater; CppSelectionChanger m_cppSelectionChanger; }; CppEditorWidgetPrivate::CppEditorWidgetPrivate(CppEditorWidget *q) : m_modelManager(CppModelManager::instance()) , m_cppEditorDocument(qobject_cast(q->textDocument())) , m_cppEditorOutline(new CppEditorOutline(q)) , m_declDefLinkFinder(new FunctionDeclDefLinkFinder(q)) , m_localRenaming(q) , m_useSelectionsUpdater(q) , m_cppSelectionChanger() {} CppEditorWidget::CppEditorWidget() : d(new CppEditorWidgetPrivate(this)) { qRegisterMetaType("CppTools::SemanticInfo"); } void CppEditorWidget::finalizeInitialization() { d->m_cppEditorDocument = qobject_cast(textDocument()); setLanguageSettingsId(CppTools::Constants::CPP_SETTINGS_ID); // clang-format off // function combo box sorting connect(CppEditorPlugin::instance(), &CppEditorPlugin::outlineSortingChanged, outline(), &CppEditorOutline::setSorted); connect(d->m_cppEditorDocument, &CppEditorDocument::codeWarningsUpdated, this, &CppEditorWidget::onCodeWarningsUpdated); connect(d->m_cppEditorDocument, &CppEditorDocument::ifdefedOutBlocksUpdated, this, &CppEditorWidget::onIfdefedOutBlocksUpdated); connect(d->m_cppEditorDocument, &CppEditorDocument::cppDocumentUpdated, this, &CppEditorWidget::onCppDocumentUpdated); connect(d->m_cppEditorDocument, &CppEditorDocument::semanticInfoUpdated, this, [this](const CppTools::SemanticInfo &info) { updateSemanticInfo(info); }); connect(d->m_declDefLinkFinder, &FunctionDeclDefLinkFinder::foundLink, this, &CppEditorWidget::onFunctionDeclDefLinkFound); connect(&d->m_useSelectionsUpdater, &CppUseSelectionsUpdater::selectionsForVariableUnderCursorUpdated, &d->m_localRenaming, &CppLocalRenaming::updateSelectionsForVariableUnderCursor); connect(&d->m_useSelectionsUpdater, &CppUseSelectionsUpdater::finished, this, [this] (SemanticInfo::LocalUseMap localUses, bool success) { if (success) { d->m_lastSemanticInfo.localUsesUpdated = true; d->m_lastSemanticInfo.localUses = localUses; } }); connect(document(), &QTextDocument::contentsChange, &d->m_localRenaming, &CppLocalRenaming::onContentsChangeOfEditorWidgetDocument); connect(&d->m_localRenaming, &CppLocalRenaming::finished, [this] { cppEditorDocument()->recalculateSemanticInfoDetached(); }); connect(&d->m_localRenaming, &CppLocalRenaming::processKeyPressNormally, this, &CppEditorWidget::processKeyNormally); connect(this, &QPlainTextEdit::cursorPositionChanged, d->m_cppEditorOutline, &CppEditorOutline::updateIndex); connect(cppEditorDocument(), &CppEditorDocument::preprocessorSettingsChanged, this, [this](bool customSettings) { updateWidgetHighlighting(d->m_preprocessorButton, customSettings); }); // set up function declaration - definition link d->m_updateFunctionDeclDefLinkTimer.setSingleShot(true); d->m_updateFunctionDeclDefLinkTimer.setInterval(UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL); connect(&d->m_updateFunctionDeclDefLinkTimer, &QTimer::timeout, this, &CppEditorWidget::updateFunctionDeclDefLinkNow); connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CppEditorWidget::updateFunctionDeclDefLink); connect(this, &QPlainTextEdit::textChanged, this, &CppEditorWidget::updateFunctionDeclDefLink); // set up the use highlighitng connect(this, &CppEditorWidget::cursorPositionChanged, this, [this]() { if (!d->m_localRenaming.isActive()) d->m_useSelectionsUpdater.scheduleUpdate(); // Notify selection expander about the changed cursor. d->m_cppSelectionChanger.onCursorPositionChanged(textCursor()); }); // Toolbar: Parse context ParseContextModel &parseContextModel = cppEditorDocument()->parseContextModel(); d->m_parseContextWidget = new ParseContextWidget(parseContextModel, this); d->m_parseContextAction = insertExtraToolBarWidget(TextEditorWidget::Left, d->m_parseContextWidget); d->m_parseContextAction->setVisible(false); connect(&parseContextModel, &ParseContextModel::updated, this, [this](bool areMultipleAvailable) { d->m_parseContextAction->setVisible(areMultipleAvailable); }); // Toolbar: Outline/Overview combo box insertExtraToolBarWidget(TextEditorWidget::Left, d->m_cppEditorOutline->widget()); // clang-format on // Toolbar: '#' Button // TODO: Make "Additional Preprocessor Directives" also useful with Clang Code Model. if (!d->m_modelManager->isClangCodeModelActive()) { d->m_preprocessorButton = new QToolButton(this); d->m_preprocessorButton->setText(QLatin1String("#")); Command *cmd = ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG); connect(cmd, &Command::keySequenceChanged, this, &CppEditorWidget::updatePreprocessorButtonTooltip); updatePreprocessorButtonTooltip(); connect(d->m_preprocessorButton, &QAbstractButton::clicked, this, &CppEditorWidget::showPreProcessorWidget); insertExtraToolBarWidget(TextEditorWidget::Left, d->m_preprocessorButton); } // Toolbar: Actions to show minimized info bars d->m_showInfoBarActions = MinimizableInfoBars::createShowInfoBarActions([this](QWidget *w) { return this->insertExtraToolBarWidget(TextEditorWidget::Left, w); }); connect(&cppEditorDocument()->minimizableInfoBars(), &MinimizableInfoBars::showAction, this, &CppEditorWidget::onShowInfoBarAction); } void CppEditorWidget::finalizeInitializationAfterDuplication(TextEditorWidget *other) { QTC_ASSERT(other, return); auto cppEditorWidget = qobject_cast(other); QTC_ASSERT(cppEditorWidget, return); if (cppEditorWidget->isSemanticInfoValidExceptLocalUses()) updateSemanticInfo(cppEditorWidget->semanticInfo()); d->m_cppEditorOutline->update(); const Id selectionKind = CodeWarningsSelection; setExtraSelections(selectionKind, cppEditorWidget->extraSelections(selectionKind)); if (isWidgetHighlighted(cppEditorWidget->d->m_preprocessorButton)) updateWidgetHighlighting(d->m_preprocessorButton, true); d->m_parseContextWidget->syncToModel(); d->m_parseContextAction->setVisible( d->m_cppEditorDocument->parseContextModel().areMultipleAvailable()); } CppEditorWidget::~CppEditorWidget() = default; CppEditorDocument *CppEditorWidget::cppEditorDocument() const { return d->m_cppEditorDocument; } CppEditorOutline *CppEditorWidget::outline() const { return d->m_cppEditorOutline; } void CppEditorWidget::paste() { if (d->m_localRenaming.handlePaste()) return; TextEditorWidget::paste(); } void CppEditorWidget::cut() { if (d->m_localRenaming.handleCut()) return; TextEditorWidget::cut(); } void CppEditorWidget::selectAll() { if (d->m_localRenaming.handleSelectAll()) return; TextEditorWidget::selectAll(); } void CppEditorWidget::onCppDocumentUpdated() { d->m_cppEditorOutline->update(); } void CppEditorWidget::onCodeWarningsUpdated(unsigned revision, const QList selections, const RefactorMarkers &refactorMarkers) { if (revision != documentRevision()) return; setExtraSelections(TextEditorWidget::CodeWarningsSelection, unselectLeadingWhitespace(selections)); setRefactorMarkers(refactorMarkers + RefactorMarker::filterOutType( this->refactorMarkers(), CppTools::Constants::CPP_CLANG_FIXIT_AVAILABLE_MARKER_ID)); } void CppEditorWidget::onIfdefedOutBlocksUpdated(unsigned revision, const QList ifdefedOutBlocks) { if (revision != documentRevision()) return; setIfdefedOutBlocks(ifdefedOutBlocks); } void CppEditorWidget::onShowInfoBarAction(const Id &id, bool show) { QAction *action = d->m_showInfoBarActions.value(id); QTC_ASSERT(action, return); action->setVisible(show); } static QString getDocumentLine(QTextDocument *document, int line) { if (document) return document->findBlockByNumber(line - 1).text(); return {}; } static std::unique_ptr getCurrentDocument(const QString &path) { const QTextCodec *defaultCodec = Core::EditorManager::defaultTextCodec(); QString contents; Utils::TextFileFormat format; QString error; if (Utils::TextFileFormat::readFile(path, defaultCodec, &contents, &format, &error) != Utils::TextFileFormat::ReadSuccess) { qWarning() << "Error reading file " << path << " : " << error; return {}; } return std::make_unique(contents); } static void onReplaceUsagesClicked(const QString &text, const QList &items, bool preserveCase) { CppModelManager *modelManager = CppModelManager::instance(); if (!modelManager) return; const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items, preserveCase); if (!fileNames.isEmpty()) { modelManager->updateSourceFiles(Utils::toSet(fileNames)); SearchResultWindow::instance()->hide(); } } static QTextDocument *getOpenDocument(const QString &path) { const IDocument *document = DocumentModel::documentForFilePath(FilePath::fromString(path)); if (document) return qobject_cast(document)->document(); return {}; } static void addSearchResults(CppTools::Usages usages, SearchResult &search, const QString &text) { std::sort(usages.begin(), usages.end()); std::unique_ptr currentDocument; QString lastPath; for (const CppTools::Usage &usage : usages) { QTextDocument *document = getOpenDocument(usage.path); if (!document) { if (usage.path != lastPath) { currentDocument = getCurrentDocument(usage.path); lastPath = usage.path; } document = currentDocument.get(); } const QString lineContent = getDocumentLine(document, usage.line); if (!lineContent.isEmpty()) { Search::TextRange range{Search::TextPosition(usage.line, usage.column - 1), Search::TextPosition(usage.line, usage.column + text.length() - 1)}; search.addResult(usage.path, lineContent, range); } } } static void findRenameCallback(CppEditorWidget *widget, const QTextCursor &baseCursor, const CppTools::Usages &usages, bool rename = false, const QString &replacement = QString()) { QTextCursor cursor = Utils::Text::wordStartCursor(baseCursor); cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); const QString text = cursor.selectedText(); SearchResultWindow::SearchMode mode = SearchResultWindow::SearchOnly; if (rename) mode = SearchResultWindow::SearchAndReplace; SearchResult *search = SearchResultWindow::instance()->startNewSearch( QObject::tr("C++ Usages:"), QString(), text, mode, SearchResultWindow::PreserveCaseDisabled, QLatin1String("CppEditor")); search->setTextToReplace(replacement); search->setSearchAgainSupported(true); QObject::connect(search, &SearchResult::replaceButtonClicked, &onReplaceUsagesClicked); QObject::connect(search, &SearchResult::searchAgainRequested, [widget, rename, replacement, baseCursor]() { rename ? widget->renameUsages(replacement, baseCursor) : widget->findUsages(baseCursor); }); addSearchResults(usages, *search, text); search->finishSearch(false); QObject::connect(search, &SearchResult::activated, [](const Core::SearchResultItem& item) { Core::EditorManager::openEditorAtSearchResult(item); }); search->popup(); } void CppEditorWidget::findUsages() { findUsages(textCursor()); } void CppEditorWidget::findUsages(QTextCursor cursor) { // 'this' in cursorInEditor is never used (and must never be used) asynchronously. const CppTools::CursorInEditor cursorInEditor{cursor, textDocument()->filePath(), this}; QPointer cppEditorWidget = this; d->m_modelManager->findUsages(cursorInEditor, [=](const CppTools::Usages &usages) { if (!cppEditorWidget) return; findRenameCallback(cppEditorWidget.data(), cursor, usages); }); } void CppEditorWidget::renameUsages(const QString &replacement, QTextCursor cursor) { if (cursor.isNull()) cursor = textCursor(); CppTools::CursorInEditor cursorInEditor{cursor, textDocument()->filePath(), this}; QPointer cppEditorWidget = this; d->m_modelManager->globalRename(cursorInEditor, [=](const CppTools::Usages &usages) { if (!cppEditorWidget) return; findRenameCallback(cppEditorWidget.data(), cursor, usages, true, replacement); }, replacement); } bool CppEditorWidget::selectBlockUp() { if (!behaviorSettings().m_smartSelectionChanging) return TextEditorWidget::selectBlockUp(); QTextCursor cursor = textCursor(); d->m_cppSelectionChanger.startChangeSelection(); const bool changed = d->m_cppSelectionChanger .changeSelection(CppSelectionChanger::ExpandSelection, cursor, d->m_lastSemanticInfo.doc); if (changed) setTextCursor(cursor); d->m_cppSelectionChanger.stopChangeSelection(); return changed; } bool CppEditorWidget::selectBlockDown() { if (!behaviorSettings().m_smartSelectionChanging) return TextEditorWidget::selectBlockDown(); QTextCursor cursor = textCursor(); d->m_cppSelectionChanger.startChangeSelection(); const bool changed = d->m_cppSelectionChanger .changeSelection(CppSelectionChanger::ShrinkSelection, cursor, d->m_lastSemanticInfo.doc); if (changed) setTextCursor(cursor); d->m_cppSelectionChanger.stopChangeSelection(); return changed; } void CppEditorWidget::updateWidgetHighlighting(QWidget *widget, bool highlight) { if (!widget) return; widget->setProperty("highlightWidget", highlight); widget->update(); } bool CppEditorWidget::isWidgetHighlighted(QWidget *widget) { return widget ? widget->property("highlightWidget").toBool() : false; } namespace { QList fetchProjectParts(CppTools::CppModelManager *modelManager, const Utils::FilePath &filePath) { QList projectParts = modelManager->projectPart(filePath); if (projectParts.isEmpty()) projectParts = modelManager->projectPartFromDependencies(filePath); if (projectParts.isEmpty()) projectParts.append(modelManager->fallbackProjectPart()); return projectParts; } ProjectPart *findProjectPartForCurrentProject(const QList &projectParts, ProjectExplorer::Project *currentProject) { auto found = std::find_if(projectParts.cbegin(), projectParts.cend(), [&](const CppTools::ProjectPart::Ptr &projectPart) { return projectPart->project == currentProject; }); if (found != projectParts.cend()) return (*found).data(); return nullptr; } } // namespace ProjectPart *CppEditorWidget::projectPart() const { if (!d->m_modelManager) return nullptr; auto projectParts = fetchProjectParts(d->m_modelManager, textDocument()->filePath()); return findProjectPartForCurrentProject(projectParts, ProjectExplorer::ProjectTree::currentProject()); } namespace { using ClangBackEnd::V2::SourceLocationContainer; using Utils::Text::selectAt; QTextCharFormat occurrencesTextCharFormat() { using TextEditor::TextEditorSettings; return TextEditorSettings::fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES); } QList sourceLocationsToExtraSelections( const std::vector &sourceLocations, uint selectionLength, CppEditorWidget *cppEditorWidget) { const auto textCharFormat = occurrencesTextCharFormat(); QList selections; selections.reserve(int(sourceLocations.size())); auto sourceLocationToExtraSelection = [&](const SourceLocationContainer &sourceLocation) { QTextEdit::ExtraSelection selection; selection.cursor = selectAt(cppEditorWidget->textCursor(), sourceLocation.line, sourceLocation.column, selectionLength); selection.format = textCharFormat; return selection; }; std::transform(sourceLocations.begin(), sourceLocations.end(), std::back_inserter(selections), sourceLocationToExtraSelection); return selections; }; } void CppEditorWidget::renameSymbolUnderCursor() { using ClangBackEnd::SourceLocationsContainer; ProjectPart *projPart = projectPart(); if (!projPart) return; if (d->m_localRenaming.isActive() && d->m_localRenaming.isSameSelection(textCursor().position())) { return; } d->m_useSelectionsUpdater.abortSchedule(); QPointer cppEditorWidget = this; auto renameSymbols = [=](const QString &symbolName, const SourceLocationsContainer &sourceLocations, int revision) { if (cppEditorWidget) { viewport()->setCursor(Qt::IBeamCursor); if (revision != document()->revision()) return; if (sourceLocations.hasContent()) { QList selections = sourceLocationsToExtraSelections(sourceLocations.sourceLocationContainers(), static_cast(symbolName.size()), cppEditorWidget); setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections); d->m_localRenaming.stop(); d->m_localRenaming.updateSelectionsForVariableUnderCursor(selections); } if (!d->m_localRenaming.start()) cppEditorWidget->renameUsages(); } }; viewport()->setCursor(Qt::BusyCursor); d->m_modelManager->startLocalRenaming(CppTools::CursorInEditor{textCursor(), textDocument()->filePath(), this}, projPart, std::move(renameSymbols)); } void CppEditorWidget::updatePreprocessorButtonTooltip() { if (!d->m_preprocessorButton) return; Command *cmd = ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG); QTC_ASSERT(cmd, return ); d->m_preprocessorButton->setToolTip(cmd->action()->toolTip()); } void CppEditorWidget::switchDeclarationDefinition(bool inNextSplit) { if (!d->m_modelManager) return; if (!d->m_lastSemanticInfo.doc) return; // Find function declaration or definition under cursor Function *functionDefinitionSymbol = nullptr; Symbol *functionDeclarationSymbol = nullptr; Symbol *declarationSymbol = nullptr; ASTPath astPathFinder(d->m_lastSemanticInfo.doc); const QList astPath = astPathFinder(textCursor()); for (AST *ast : astPath) { if (FunctionDefinitionAST *functionDefinitionAST = ast->asFunctionDefinition()) { if ((functionDefinitionSymbol = functionDefinitionAST->symbol)) break; // Function definition found! } else if (SimpleDeclarationAST *simpleDeclaration = ast->asSimpleDeclaration()) { if (List *symbols = simpleDeclaration->symbols) { if (Symbol *symbol = symbols->value) { if (symbol->isDeclaration()) { declarationSymbol = symbol; if (symbol->type()->isFunctionType()) { functionDeclarationSymbol = symbol; break; // Function declaration found! } } } } } } // Link to function definition/declaration Utils::Link symbolLink; if (functionDeclarationSymbol) { Symbol *symbol = d->m_modelManager->symbolFinder() ->findMatchingDefinition(functionDeclarationSymbol, d->m_modelManager->snapshot()); if (symbol) symbolLink = symbol->toLink(); } else if (declarationSymbol) { Symbol *symbol = d->m_modelManager->symbolFinder() ->findMatchingVarDefinition(declarationSymbol, d->m_modelManager->snapshot()); if (symbol) symbolLink = symbol->toLink(); } else if (functionDefinitionSymbol) { const Snapshot snapshot = d->m_modelManager->snapshot(); LookupContext context(d->m_lastSemanticInfo.doc, snapshot); ClassOrNamespace *binding = context.lookupType(functionDefinitionSymbol); const QList declarations = context.lookup(functionDefinitionSymbol->name(), functionDefinitionSymbol->enclosingScope()); QList best; foreach (const LookupItem &r, declarations) { if (Symbol *decl = r.declaration()) { if (Function *funTy = decl->type()->asFunctionType()) { if (funTy->match(functionDefinitionSymbol)) { if (decl != functionDefinitionSymbol && binding == r.binding()) best.prepend(decl); else best.append(decl); } } } } if (best.isEmpty()) return; symbolLink = best.first()->toLink(); } // Open Editor at link position if (symbolLink.hasValidTarget()) openLink(symbolLink, inNextSplit != alwaysOpenLinksInNextSplit()); } void CppEditorWidget::findLinkAt(const QTextCursor &cursor, Utils::ProcessLinkCallback &&processLinkCallback, bool resolveTarget, bool inNextSplit) { if (!d->m_modelManager) return processLinkCallback(Utils::Link()); const Utils::FilePath &filePath = textDocument()->filePath(); followSymbolInterface().findLink(CppTools::CursorInEditor{cursor, filePath, this}, std::move(processLinkCallback), resolveTarget, d->m_modelManager->snapshot(), d->m_lastSemanticInfo.doc, d->m_modelManager->symbolFinder(), inNextSplit); } unsigned CppEditorWidget::documentRevision() const { return document()->revision(); } CppTools::FollowSymbolInterface &CppEditorWidget::followSymbolInterface() const { return d->m_modelManager->followSymbolInterface(); } bool CppEditorWidget::isSemanticInfoValidExceptLocalUses() const { return d->m_lastSemanticInfo.doc && d->m_lastSemanticInfo.revision == documentRevision() && !d->m_lastSemanticInfo.snapshot.isEmpty(); } bool CppEditorWidget::isSemanticInfoValid() const { return isSemanticInfoValidExceptLocalUses() && d->m_lastSemanticInfo.localUsesUpdated; } SemanticInfo CppEditorWidget::semanticInfo() const { return d->m_lastSemanticInfo; } bool CppEditorWidget::event(QEvent *e) { switch (e->type()) { case QEvent::ShortcutOverride: // handle escape manually if a rename is active if (static_cast(e)->key() == Qt::Key_Escape && d->m_localRenaming.isActive()) { e->accept(); return true; } break; default: break; } return TextEditorWidget::event(e); } void CppEditorWidget::processKeyNormally(QKeyEvent *e) { TextEditorWidget::keyPressEvent(e); } static void addRefactoringActions(QMenu *menu, AssistInterface *iface) { if (!iface || !menu) return; using Processor = QScopedPointer; using Proposal = QScopedPointer; const Processor processor(CppEditorPlugin::instance()->quickFixProvider()->createProcessor()); const Proposal proposal(processor->perform(iface)); // OK, perform() takes ownership of iface. if (proposal) { auto model = proposal->model().staticCast(); for (int index = 0; index < model->size(); ++index) { const auto item = static_cast(model->proposalItem(index)); const QuickFixOperation::Ptr op = item->data().value(); const QAction *action = menu->addAction(op->description()); QObject::connect(action, &QAction::triggered, menu, [op] { op->perform(); }); } } } class ProgressIndicatorMenuItem : public QWidgetAction { Q_OBJECT public: ProgressIndicatorMenuItem(QObject *parent) : QWidgetAction(parent) {} protected: QWidget *createWidget(QWidget *parent = nullptr) override { return new Utils::ProgressIndicator(Utils::ProgressIndicatorSize::Small, parent); } }; QMenu *CppEditorWidget::createRefactorMenu(QWidget *parent) const { auto *menu = new QMenu(tr("&Refactor"), parent); menu->addAction(ActionManager::command(TextEditor::Constants::RENAME_SYMBOL)->action()); // ### enable // updateSemanticInfo(m_semanticHighlighter->semanticInfo(currentSource())); if (isSemanticInfoValidExceptLocalUses()) { d->m_useSelectionsUpdater.abortSchedule(); const CppUseSelectionsUpdater::RunnerInfo runnerInfo = d->m_useSelectionsUpdater.update(); switch (runnerInfo) { case CppUseSelectionsUpdater::RunnerInfo::AlreadyUpToDate: addRefactoringActions(menu, createAssistInterface(QuickFix, ExplicitlyInvoked)); break; case CppUseSelectionsUpdater::RunnerInfo::Started: { // Update the refactor menu once we get the results. auto *progressIndicatorMenuItem = new ProgressIndicatorMenuItem(menu); menu->addAction(progressIndicatorMenuItem); connect(&d->m_useSelectionsUpdater, &CppUseSelectionsUpdater::finished, menu, [=] (SemanticInfo::LocalUseMap, bool success) { QTC_CHECK(success); menu->removeAction(progressIndicatorMenuItem); addRefactoringActions(menu, createAssistInterface(QuickFix, ExplicitlyInvoked)); }); break; } case CppUseSelectionsUpdater::RunnerInfo::FailedToStart: case CppUseSelectionsUpdater::RunnerInfo::Invalid: QTC_CHECK(false && "Unexpected CppUseSelectionsUpdater runner result"); } } return menu; } static void appendCustomContextMenuActionsAndMenus(QMenu *menu, QMenu *refactorMenu) { bool isRefactoringMenuAdded = false; const QMenu *contextMenu = ActionManager::actionContainer(Constants::M_CONTEXT)->menu(); for (QAction *action : contextMenu->actions()) { menu->addAction(action); if (action->objectName() == Constants::M_REFACTORING_MENU_INSERTION_POINT) { isRefactoringMenuAdded = true; menu->addMenu(refactorMenu); } } QTC_CHECK(isRefactoringMenuAdded); } void CppEditorWidget::contextMenuEvent(QContextMenuEvent *e) { const QPointer menu(new QMenu(this)); appendCustomContextMenuActionsAndMenus(menu, createRefactorMenu(menu)); appendStandardContextMenuActions(menu); menu->exec(e->globalPos()); if (menu) delete menu; // OK, menu was not already deleted by closed editor widget. } void CppEditorWidget::keyPressEvent(QKeyEvent *e) { if (d->m_localRenaming.handleKeyPressEvent(e)) return; if (handleStringSplitting(e)) return; if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { if (trySplitComment(this, semanticInfo().snapshot)) { e->accept(); return; } } TextEditorWidget::keyPressEvent(e); } bool CppEditorWidget::handleStringSplitting(QKeyEvent *e) const { if (!TextEditorSettings::completionSettings().m_autoSplitStrings) return false; if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { QTextCursor cursor = textCursor(); const Kind stringKind = CPlusPlus::MatchingText::stringKindAtCursor(cursor); if (stringKind >= T_FIRST_STRING_LITERAL && stringKind < T_FIRST_RAW_STRING_LITERAL) { cursor.beginEditBlock(); if (cursor.positionInBlock() > 0 && cursor.block().text().at(cursor.positionInBlock() - 1) == QLatin1Char('\\')) { // Already escaped: simply go back to line, but do not indent. cursor.insertText(QLatin1String("\n")); } else if (e->modifiers() & Qt::ShiftModifier) { // With 'shift' modifier, escape the end of line character // and start at beginning of next line. cursor.insertText(QLatin1String("\\\n")); } else { // End the current string, and start a new one on the line, properly indented. cursor.insertText(QLatin1String("\"\n\"")); textDocument()->autoIndent(cursor); } cursor.endEditBlock(); e->accept(); return true; } } return false; } void CppEditorWidget::slotCodeStyleSettingsChanged(const QVariant &) { QtStyleCodeFormatter formatter; formatter.invalidateCache(document()); } void CppEditorWidget::updateSemanticInfo() { updateSemanticInfo(d->m_cppEditorDocument->recalculateSemanticInfo(), /*updateUseSelectionSynchronously=*/ true); } void CppEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo, bool updateUseSelectionSynchronously) { if (semanticInfo.revision != documentRevision()) return; d->m_lastSemanticInfo = semanticInfo; if (!d->m_localRenaming.isActive()) { const CppUseSelectionsUpdater::CallType type = updateUseSelectionSynchronously ? CppUseSelectionsUpdater::CallType::Synchronous : CppUseSelectionsUpdater::CallType::Asynchronous; d->m_useSelectionsUpdater.update(type); } // schedule a check for a decl/def link updateFunctionDeclDefLink(); } AssistInterface *CppEditorWidget::createAssistInterface(AssistKind kind, AssistReason reason) const { if (kind == Completion || kind == FunctionHint) { CppCompletionAssistProvider * const cap = kind == Completion ? cppEditorDocument()->completionAssistProvider() : cppEditorDocument()->functionHintAssistProvider(); if (cap) { LanguageFeatures features = LanguageFeatures::defaultFeatures(); if (Document::Ptr doc = d->m_lastSemanticInfo.doc) features = doc->languageFeatures(); features.objCEnabled |= cppEditorDocument()->isObjCEnabled(); return cap->createAssistInterface(textDocument()->filePath(), this, features, position(), reason); } } else if (kind == QuickFix) { if (isSemanticInfoValid()) return new CppQuickFixInterface(const_cast(this), reason); } else { return TextEditorWidget::createAssistInterface(kind, reason); } return nullptr; } QSharedPointer CppEditorWidget::declDefLink() const { return d->m_declDefLink; } void CppEditorWidget::updateFunctionDeclDefLink() { const int pos = textCursor().selectionStart(); // if there's already a link, abort it if the cursor is outside or the name changed // (adding a prefix is an exception since the user might type a return type) if (d->m_declDefLink && (pos < d->m_declDefLink->linkSelection.selectionStart() || pos > d->m_declDefLink->linkSelection.selectionEnd() || !d->m_declDefLink->nameSelection.selectedText().trimmed().endsWith( d->m_declDefLink->nameInitial))) { abortDeclDefLink(); return; } // don't start a new scan if there's one active and the cursor is already in the scanned area const QTextCursor scannedSelection = d->m_declDefLinkFinder->scannedSelection(); if (!scannedSelection.isNull() && scannedSelection.selectionStart() <= pos && scannedSelection.selectionEnd() >= pos) { return; } d->m_updateFunctionDeclDefLinkTimer.start(); } void CppEditorWidget::updateFunctionDeclDefLinkNow() { IEditor *editor = EditorManager::currentEditor(); if (!editor || editor->widget() != this) return; const Snapshot semanticSnapshot = d->m_lastSemanticInfo.snapshot; const Document::Ptr semanticDoc = d->m_lastSemanticInfo.doc; if (d->m_declDefLink) { // update the change marker const Utils::ChangeSet changes = d->m_declDefLink->changes(semanticSnapshot); if (changes.isEmpty()) d->m_declDefLink->hideMarker(this); else d->m_declDefLink->showMarker(this); return; } if (!isSemanticInfoValidExceptLocalUses()) return; Snapshot snapshot = d->m_modelManager->snapshot(); snapshot.insert(semanticDoc); d->m_declDefLinkFinder->startFindLinkAt(textCursor(), semanticDoc, snapshot); } void CppEditorWidget::onFunctionDeclDefLinkFound(QSharedPointer link) { abortDeclDefLink(); d->m_declDefLink = link; IDocument *targetDocument = DocumentModel::documentForFilePath( FilePath::fromString(d->m_declDefLink->targetFile->fileName())); if (textDocument() != targetDocument) { if (auto textDocument = qobject_cast(targetDocument)) connect(textDocument, &IDocument::contentsChanged, this, &CppEditorWidget::abortDeclDefLink); } } void CppEditorWidget::applyDeclDefLinkChanges(bool jumpToMatch) { if (!d->m_declDefLink) return; d->m_declDefLink->apply(this, jumpToMatch); abortDeclDefLink(); updateFunctionDeclDefLink(); } void CppEditorWidget::encourageApply() { if (d->m_localRenaming.encourageApply()) return; TextEditorWidget::encourageApply(); } void CppEditorWidget::abortDeclDefLink() { if (!d->m_declDefLink) return; IDocument *targetDocument = DocumentModel::documentForFilePath( FilePath::fromString(d->m_declDefLink->targetFile->fileName())); if (textDocument() != targetDocument) { if (auto textDocument = qobject_cast(targetDocument)) disconnect(textDocument, &IDocument::contentsChanged, this, &CppEditorWidget::abortDeclDefLink); } d->m_declDefLink->hideMarker(this); d->m_declDefLink.clear(); } void CppEditorWidget::showPreProcessorWidget() { const QString filePath = textDocument()->filePath().toString(); CppPreProcessorDialog dialog(filePath, this); if (dialog.exec() == QDialog::Accepted) { const QByteArray extraDirectives = dialog.extraPreprocessorDirectives().toUtf8(); cppEditorDocument()->setExtraPreprocessorDirectives(extraDirectives); cppEditorDocument()->scheduleProcessDocument(); } } void CppEditorWidget::invokeTextEditorWidgetAssist(TextEditor::AssistKind assistKind, TextEditor::IAssistProvider *provider) { invokeAssist(assistKind, provider); } const QList CppEditorWidget::unselectLeadingWhitespace( const QList &selections) { QList filtered; for (const QTextEdit::ExtraSelection &sel : selections) { QList splitSelections; int firstNonWhitespacePos = -1; int lastNonWhitespacePos = -1; bool split = false; const QTextBlock firstBlock = sel.cursor.document()->findBlock(sel.cursor.selectionStart()); bool inIndentation = firstBlock.position() == sel.cursor.selectionStart(); const auto createSplitSelection = [&] { QTextEdit::ExtraSelection newSelection; newSelection.cursor = QTextCursor(sel.cursor.document()); newSelection.cursor.setPosition(firstNonWhitespacePos); newSelection.cursor.setPosition(lastNonWhitespacePos + 1, QTextCursor::KeepAnchor); newSelection.format = sel.format; splitSelections << newSelection; }; for (int i = sel.cursor.selectionStart(); i < sel.cursor.selectionEnd(); ++i) { const QChar curChar = sel.cursor.document()->characterAt(i); if (!curChar.isSpace()) { if (firstNonWhitespacePos == -1) firstNonWhitespacePos = i; lastNonWhitespacePos = i; } if (!inIndentation) { if (curChar == QChar::ParagraphSeparator) inIndentation = true; continue; } if (curChar == QChar::ParagraphSeparator) continue; if (curChar.isSpace()) { if (firstNonWhitespacePos != -1) { createSplitSelection(); firstNonWhitespacePos = -1; lastNonWhitespacePos = -1; } split = true; continue; } inIndentation = false; } if (!split) { filtered << sel; continue; } if (firstNonWhitespacePos != -1) createSplitSelection(); filtered << splitSelections; } return filtered; } } // namespace Internal } // namespace CppEditor #include "cppeditorwidget.moc"