From eaecac2fd91de739d87e232807349589f5beb5ef Mon Sep 17 00:00:00 2001 From: Nikolai Kosjar Date: Tue, 20 May 2014 12:24:32 -0400 Subject: [PATCH] CppEditor: Extract CppLocalRenaming Now CppEditorWidget is much less convoluted with the local rename mode. This class can be reused since there is no dependency on CPPEditorWidget. Change-Id: If513e59a03214cf9f2e3831f7e3616c001e1f036 Reviewed-by: Erik Verbruggen --- src/plugins/cppeditor/cppeditor.cpp | 300 ++++-------------- src/plugins/cppeditor/cppeditor.h | 14 +- src/plugins/cppeditor/cppeditor.pro | 2 + src/plugins/cppeditor/cppeditor.qbs | 1 + src/plugins/cppeditor/cpplocalrenaming.cpp | 335 +++++++++++++++++++++ src/plugins/cppeditor/cpplocalrenaming.h | 109 +++++++ 6 files changed, 517 insertions(+), 244 deletions(-) create mode 100644 src/plugins/cppeditor/cpplocalrenaming.cpp create mode 100644 src/plugins/cppeditor/cpplocalrenaming.h diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index 3f3d707cc0b..c9922814df2 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -34,6 +34,7 @@ #include "cppeditorplugin.h" #include "cppfollowsymbolundercursor.h" #include "cpphighlighter.h" +#include "cpplocalrenaming.h" #include "cpppreprocessordialog.h" #include "cppquickfixassistant.h" @@ -437,12 +438,7 @@ public: QTimer *m_updateFunctionDeclDefLinkTimer; QHash m_semanticHighlightFormatMap; - QList m_renameSelections; - int m_currentRenameSelection; - static const int NoCurrentRenameSelection = -1; - bool m_inRename, m_inRenameChanged, m_firstRenameChange; - QTextCursor m_currentRenameSelectionBegin; - QTextCursor m_currentRenameSelectionEnd; + CppLocalRenaming m_localRenaming; CppTools::SemanticInfo m_lastSemanticInfo; QList m_quickFixes; @@ -467,10 +463,7 @@ CPPEditorWidgetPrivate::CPPEditorWidgetPrivate(CPPEditorWidget *q) : q(q) , m_modelManager(CppModelManagerInterface::instance()) , m_cppEditorDocument(qobject_cast(q->baseTextDocument())) - , m_currentRenameSelection(NoCurrentRenameSelection) - , m_inRename(false) - , m_inRenameChanged(false) - , m_firstRenameChange(false) + , m_localRenaming(q) , m_highlightRevision(0) , m_referencesRevision(0) , m_referencesCursorPosition(0) @@ -536,7 +529,11 @@ void CPPEditorWidget::ctor() connect(baseTextDocument(), SIGNAL(filePathChanged(QString,QString)), this, SLOT(onFilePathChanged())); - onFilePathChanged(); + + connect(&d->m_localRenaming, SIGNAL(finished()), + this, SLOT(onLocalRenamingFinished())); + connect(&d->m_localRenaming, SIGNAL(processKeyPressNormally(QKeyEvent*)), + this, SLOT(onLocalRenamingProcessKeyPressNormally(QKeyEvent*))); } CPPEditorWidget::~CPPEditorWidget() @@ -627,99 +624,28 @@ void CPPEditorWidget::createToolBar(CPPEditor *editor) void CPPEditorWidget::paste() { - if (d->m_currentRenameSelection == d->NoCurrentRenameSelection) { - BaseTextEditorWidget::paste(); + if (d->m_localRenaming.handlePaste()) return; - } - startRename(); BaseTextEditorWidget::paste(); - finishRename(); } void CPPEditorWidget::cut() { - if (d->m_currentRenameSelection == d->NoCurrentRenameSelection) { - BaseTextEditorWidget::cut(); + if (d->m_localRenaming.handlePaste()) return; - } - startRename(); BaseTextEditorWidget::cut(); - finishRename(); } void CPPEditorWidget::selectAll() { - // if we are currently renaming a symbol - // and the cursor is over that symbol, select just that symbol - if (d->m_currentRenameSelection != d->NoCurrentRenameSelection) { - QTextCursor cursor = textCursor(); - int selectionBegin = d->m_currentRenameSelectionBegin.position(); - int selectionEnd = d->m_currentRenameSelectionEnd.position(); - - if (cursor.position() >= selectionBegin - && cursor.position() <= selectionEnd) { - cursor.setPosition(selectionBegin); - cursor.setPosition(selectionEnd, QTextCursor::KeepAnchor); - setTextCursor(cursor); - return; - } - } + if (d->m_localRenaming.handleSelectAll()) + return; BaseTextEditorWidget::selectAll(); } -void CPPEditorWidget::startRename() -{ - d->m_inRenameChanged = false; -} - -void CPPEditorWidget::finishRename() -{ - if (!d->m_inRenameChanged) - return; - - d->m_inRename = true; - - QTextCursor cursor = textCursor(); - cursor.joinPreviousEditBlock(); - - cursor.setPosition(d->m_currentRenameSelectionEnd.position()); - cursor.setPosition(d->m_currentRenameSelectionBegin.position(), QTextCursor::KeepAnchor); - d->m_renameSelections[d->m_currentRenameSelection].cursor = cursor; - QString text = cursor.selectedText(); - - for (int i = 0; i < d->m_renameSelections.size(); ++i) { - if (i == d->m_currentRenameSelection) - continue; - QTextEdit::ExtraSelection &s = d->m_renameSelections[i]; - int pos = s.cursor.selectionStart(); - s.cursor.removeSelectedText(); - s.cursor.insertText(text); - s.cursor.setPosition(pos, QTextCursor::KeepAnchor); - } - - setExtraSelections(CodeSemanticsSelection, d->m_renameSelections); - cursor.endEditBlock(); - - d->m_inRename = false; -} - -void CPPEditorWidget::abortRename() -{ - if (d->m_currentRenameSelection <= d->NoCurrentRenameSelection) - return; - d->m_renameSelections[d->m_currentRenameSelection].format - = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES); - d->m_currentRenameSelection = d->NoCurrentRenameSelection; - d->m_currentRenameSelectionBegin = QTextCursor(); - d->m_currentRenameSelectionEnd = QTextCursor(); - setExtraSelections(CodeSemanticsSelection, d->m_renameSelections); - - semanticRehighlight(/* force = */ true); -} - /// \brief Called by \c CppEditorSupport when the document corresponding to the /// file in this editor is updated. void CPPEditorWidget::onDocumentUpdated() @@ -811,8 +737,7 @@ void CPPEditorWidget::markSymbolsNow() cursor.setPosition(cursor.position() + len, QTextCursor::KeepAnchor); QTextEdit::ExtraSelection sel; - sel.format = baseTextDocument()->fontSettings() - .toTextCharFormat(TextEditor::C_OCCURRENCES); + sel.format = textCharFormat(TextEditor::C_OCCURRENCES); sel.cursor = cursor; selections.append(sel); } @@ -838,12 +763,11 @@ static QList lazyFindReferences(Scope *scope, QString code, Document::Ptr d void CPPEditorWidget::markSymbols(const QTextCursor &tc, const SemanticInfo &info) { - abortRename(); + d->m_localRenaming.stop(); if (!info.doc) return; - const QTextCharFormat &occurrencesFormat - = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES); + const QTextCharFormat &occurrencesFormat = textCharFormat(TextEditor::C_OCCURRENCES); if (const Macro *macro = findCanonicalMacro(textCursor(), info.doc)) { QList selections; @@ -907,52 +831,17 @@ void CPPEditorWidget::renameSymbolUnderCursor() if (!d->m_modelManager) return; - CppEditorSupport *edSup = d->m_modelManager->cppEditorSupport(editor()); - updateSemanticInfo(edSup->recalculateSemanticInfo()); - abortRename(); + CppEditorSupport *ces = d->m_modelManager->cppEditorSupport(editor()); + updateSemanticInfo(ces->recalculateSemanticInfo()); - QTextCursor c = textCursor(); - - for (int i = 0; i < d->m_renameSelections.size(); ++i) { - QTextEdit::ExtraSelection s = d->m_renameSelections.at(i); - if (c.position() >= s.cursor.anchor() - && c.position() <= s.cursor.position()) { - d->m_currentRenameSelection = i; - d->m_firstRenameChange = true; - d->m_currentRenameSelectionBegin = QTextCursor(c.document()->docHandle(), - d->m_renameSelections[i].cursor.selectionStart()); - d->m_currentRenameSelectionEnd = QTextCursor(c.document()->docHandle(), - d->m_renameSelections[i].cursor.selectionEnd()); - d->m_renameSelections[i].format - = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES_RENAME);; - setExtraSelections(CodeSemanticsSelection, d->m_renameSelections); - break; - } - } - - if (d->m_renameSelections.isEmpty()) - renameUsages(); + if (!d->m_localRenaming.start()) // Rename local symbol + renameUsages(); // Rename non-local symbol or macro } void CPPEditorWidget::onContentsChanged(int position, int charsRemoved, int charsAdded) { - if (d->m_currentRenameSelection == d->NoCurrentRenameSelection || d->m_inRename) - return; - - if (position + charsAdded == d->m_currentRenameSelectionBegin.position()) { - // we are inserting at the beginning of the rename selection => expand - d->m_currentRenameSelectionBegin.setPosition(position); - d->m_renameSelections[d->m_currentRenameSelection].cursor.setPosition(position, - QTextCursor::KeepAnchor); - } - - // the condition looks odd, but keep in mind that the begin - // and end cursors do move automatically - d->m_inRenameChanged = (position >= d->m_currentRenameSelectionBegin.position() - && position + charsAdded <= d->m_currentRenameSelectionEnd.position()); - - if (!d->m_inRenameChanged) - abortRename(); + Q_UNUSED(position) + Q_UNUSED(charsAdded) if (charsRemoved > 0) updateUses(); @@ -1025,13 +914,11 @@ void CPPEditorWidget::updateOutlineIndex() d->m_updateOutlineIndexTimer->start(); } -void CPPEditorWidget::highlightUses(const QList &uses, - QList *selections) +QList CPPEditorWidget::createSelectionsFromUses( + const QList &uses) { - bool isUnused = false; - - if (uses.size() == 1) - isUnused = true; + QList result; + const bool isUnused = uses.size() == 1; foreach (const SemanticInfo::Use &use, uses) { if (use.isInvalid()) @@ -1039,19 +926,21 @@ void CPPEditorWidget::highlightUses(const QList &uses, QTextEdit::ExtraSelection sel; if (isUnused) - sel.format = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES_UNUSED); + sel.format = textCharFormat(TextEditor::C_OCCURRENCES_UNUSED); else - sel.format = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES); + sel.format = textCharFormat(TextEditor::C_OCCURRENCES); - const int anchor = document()->findBlockByNumber(use.line - 1).position() + use.column - 1; - const int position = anchor + use.length; + const int position = document()->findBlockByNumber(use.line - 1).position() + use.column - 1; + const int anchor = position + use.length; sel.cursor = QTextCursor(document()); sel.cursor.setPosition(anchor); sel.cursor.setPosition(position, QTextCursor::KeepAnchor); - selections->append(sel); + result.append(sel); } + + return result; } void CPPEditorWidget::updateOutlineIndexNow() @@ -1094,7 +983,7 @@ void CPPEditorWidget::updateUses() void CPPEditorWidget::updateUsesNow() { - if (d->m_currentRenameSelection != d->NoCurrentRenameSelection) + if (d->m_localRenaming.isActive()) return; semanticRehighlight(); @@ -1258,8 +1147,7 @@ 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_currentRenameSelection != d->NoCurrentRenameSelection) { + if (static_cast(e)->key() == Qt::Key_Escape && d->m_localRenaming.isActive()) { e->accept(); return true; } @@ -1335,88 +1223,11 @@ void CPPEditorWidget::contextMenuEvent(QContextMenuEvent *e) void CPPEditorWidget::keyPressEvent(QKeyEvent *e) { - if (d->m_currentRenameSelection == d->NoCurrentRenameSelection) { - if (!handleDocumentationComment(e)) - TextEditor::BaseTextEditorWidget::keyPressEvent(e); + if (d->m_localRenaming.handleKeyPressEvent(e)) return; - } - // key handling for renames - - QTextCursor cursor = textCursor(); - const QTextCursor::MoveMode moveMode = (e->modifiers() & Qt::ShiftModifier) - ? QTextCursor::KeepAnchor - : QTextCursor::MoveAnchor; - - switch (e->key()) { - case Qt::Key_Enter: - case Qt::Key_Return: - case Qt::Key_Escape: - abortRename(); - e->accept(); - return; - case Qt::Key_Home: { - // Send home to start of name when within the name and not at the start - if (cursor.position() > d->m_currentRenameSelectionBegin.position() - && cursor.position() <= d->m_currentRenameSelectionEnd.position()) { - cursor.setPosition(d->m_currentRenameSelectionBegin.position(), moveMode); - setTextCursor(cursor); - e->accept(); - return; - } - break; - } - case Qt::Key_End: { - // Send end to end of name when within the name and not at the end - if (cursor.position() >= d->m_currentRenameSelectionBegin.position() - && cursor.position() < d->m_currentRenameSelectionEnd.position()) { - cursor.setPosition(d->m_currentRenameSelectionEnd.position(), moveMode); - setTextCursor(cursor); - e->accept(); - return; - } - break; - } - case Qt::Key_Backspace: { - if (cursor.position() == d->m_currentRenameSelectionBegin.position() - && !cursor.hasSelection()) { - // Eat backspace at start of name when there is no selection - e->accept(); - return; - } - break; - } - case Qt::Key_Delete: { - if (cursor.position() == d->m_currentRenameSelectionEnd.position() - && !cursor.hasSelection()) { - // Eat delete at end of name when there is no selection - e->accept(); - return; - } - break; - } - default: { - break; - } - } // switch - - startRename(); - - bool wantEditBlock = (cursor.position() >= d->m_currentRenameSelectionBegin.position() - && cursor.position() <= d->m_currentRenameSelectionEnd.position()); - - if (wantEditBlock) { - // possible change inside rename selection - if (d->m_firstRenameChange) - cursor.beginEditBlock(); - else - cursor.joinPreviousEditBlock(); - d->m_firstRenameChange = false; - } - TextEditor::BaseTextEditorWidget::keyPressEvent(e); - if (wantEditBlock) - cursor.endEditBlock(); - finishRename(); + if (!handleDocumentationComment(e)) + TextEditor::BaseTextEditorWidget::keyPressEvent(e); } Core::IEditor *CPPEditor::duplicate() @@ -1559,9 +1370,7 @@ void CPPEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo) convertPosition(position(), &line, &column); QList unusedSelections; - - d->m_renameSelections.clear(); - d->m_currentRenameSelection = d->NoCurrentRenameSelection; + QList selections; // We can use the semanticInfo's snapshot (and avoid locking), but not its // document, since it doesn't contain expanded macros. @@ -1584,21 +1393,21 @@ void CPPEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo) } if (uses.size() == 1) { - if (!CppTools::isOwnershipRAIIType(it.key(), context)) { - // it's an unused declaration - highlightUses(uses, &unusedSelections); - } - } else if (good && d->m_renameSelections.isEmpty()) { - highlightUses(uses, &d->m_renameSelections); + if (!CppTools::isOwnershipRAIIType(it.key(), context)) + unusedSelections << createSelectionsFromUses(uses); // unused declaration + } else if (good && selections.isEmpty()) { + selections << createSelectionsFromUses(uses); } } setExtraSelections(UnusedSymbolSelection, unusedSelections); - if (!d->m_renameSelections.isEmpty()) - setExtraSelections(CodeSemanticsSelection, d->m_renameSelections); // ### - else + if (!selections.isEmpty()) { + setExtraSelections(CodeSemanticsSelection, selections); + d->m_localRenaming.updateLocalUseSelections(selections); + } else { markSymbols(textCursor(), semanticInfo); + } d->m_lastSemanticInfo.forced = false; // clear the forced flag @@ -1775,6 +1584,16 @@ void CPPEditorWidget::abortDeclDefLink() d->m_declDefLink.clear(); } +void CPPEditorWidget::onLocalRenamingFinished() +{ + semanticRehighlight(true); +} + +void CPPEditorWidget::onLocalRenamingProcessKeyPressNormally(QKeyEvent *e) +{ + BaseTextEditorWidget::keyPressEvent(e); +} + bool CPPEditorWidget::handleDocumentationComment(QKeyEvent *e) { if (!d->m_commentsSettings.m_enableDoxygen @@ -1866,6 +1685,11 @@ bool CPPEditorWidget::isStartOfDoxygenComment(const QTextCursor &cursor) const return false; } +QTextCharFormat CPPEditorWidget::textCharFormat(TextEditor::TextStyle category) +{ + return baseTextDocument()->fontSettings().toTextCharFormat(category); +} + void CPPEditorWidget::onCommentsSettingsChanged(const CppTools::CommentsSettings &settings) { d->m_commentsSettings = settings; diff --git a/src/plugins/cppeditor/cppeditor.h b/src/plugins/cppeditor/cppeditor.h index 0a88efff6a8..11384c1c0ee 100644 --- a/src/plugins/cppeditor/cppeditor.h +++ b/src/plugins/cppeditor/cppeditor.h @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -171,6 +172,9 @@ private slots: void onCommentsSettingsChanged(const CppTools::CommentsSettings &settings); void abortDeclDefLink(); + void onLocalRenamingFinished(); + void onLocalRenamingProcessKeyPressNormally(QKeyEvent *e); + private: static bool openCppEditorAt(const Link &, bool inNextSplit = false); @@ -185,15 +189,13 @@ private: const CPlusPlus::Macro *findCanonicalMacro(const QTextCursor &cursor, CPlusPlus::Document::Ptr doc) const; + QTextCharFormat textCharFormat(TextEditor::TextStyle category); + void markSymbols(const QTextCursor &tc, const CppTools::SemanticInfo &info); bool sortedOutline() const; - void highlightUses(const QList &uses, - QList *selections); - - void startRename(); - void finishRename(); - void abortRename(); + QList createSelectionsFromUses( + const QList &uses); QModelIndex indexForPosition(int line, int column, const QModelIndex &rootIndex = QModelIndex()) const; diff --git a/src/plugins/cppeditor/cppeditor.pro b/src/plugins/cppeditor/cppeditor.pro index 28a5342d2f9..bcaeea86285 100644 --- a/src/plugins/cppeditor/cppeditor.pro +++ b/src/plugins/cppeditor/cppeditor.pro @@ -23,6 +23,7 @@ HEADERS += \ cppincludehierarchymodel.h \ cppincludehierarchytreeview.h \ cppinsertvirtualmethods.h \ + cpplocalrenaming.h \ cppoutline.h \ cpppreprocessordialog.h \ cppquickfix.h \ @@ -52,6 +53,7 @@ SOURCES += \ cppincludehierarchymodel.cpp \ cppincludehierarchytreeview.cpp \ cppinsertvirtualmethods.cpp \ + cpplocalrenaming.cpp \ cppoutline.cpp \ cpppreprocessordialog.cpp \ cppquickfix.cpp \ diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index 10d4be92e64..6a55755d8a8 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -42,6 +42,7 @@ QtcPlugin { "cppincludehierarchytreeview.cpp", "cppincludehierarchytreeview.h", "cppinsertvirtualmethods.cpp", "cppinsertvirtualmethods.h", + "cpplocalrenaming.cpp", "cpplocalrenaming.h", "cppoutline.cpp", "cppoutline.h", "cpppreprocessordialog.cpp", "cpppreprocessordialog.h", "cpppreprocessordialog.ui", "cppquickfix.cpp", "cppquickfix.h", diff --git a/src/plugins/cppeditor/cpplocalrenaming.cpp b/src/plugins/cppeditor/cpplocalrenaming.cpp new file mode 100644 index 00000000000..4a5dd34644e --- /dev/null +++ b/src/plugins/cppeditor/cpplocalrenaming.cpp @@ -0,0 +1,335 @@ +/**************************************************************************** +** +** 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 "cpplocalrenaming.h" + +#include +#include + +#include + +/*! + \class CppEditor::Internal::CppLocalRenaming + \brief A helper class of CPPEditorWidget that implements renaming local usages. + + \internal + + Local use selections must be first set/updated with updateLocalUseSelections(). + Afterwards the local renaming can be started with start(). The CPPEditorWidget + can then delegate work related to the local renaming mode to the handle* + functions. + + \sa CppEditor::Internal::CPPEditorWidget + */ + +namespace { + +void modifyCursorSelection(QTextCursor &cursor, int position, int anchor) +{ + cursor.setPosition(anchor); + cursor.setPosition(position, QTextCursor::KeepAnchor); +} + +} // anonymous namespace + +namespace CppEditor { +namespace Internal { + +CppLocalRenaming::CppLocalRenaming(TextEditor::BaseTextEditorWidget *editorWidget) + : m_editorWidget(editorWidget) + , m_modifyingSelections(false) + , m_renameSelectionChanged(false) + , m_firstRenameChangeExpected(false) +{ + forgetRenamingSelection(); + connect(m_editorWidget->document(), SIGNAL(contentsChange(int,int,int)), + this, SLOT(onContentsChangeOfEditorWidgetDocument(int,int,int))); +} + +void CppLocalRenaming::updateLocalUseSelections(const QList &selections) +{ + QTC_ASSERT(!isActive(), return); + m_selections = selections; +} + +bool CppLocalRenaming::start() +{ + stop(); + + if (findRenameSelection(m_editorWidget->textCursor().position())) { + updateRenamingSelectionFormat(textCharFormat(TextEditor::C_OCCURRENCES_RENAME)); + m_firstRenameChangeExpected = true; + updateEditorWidgetWithSelections(); + return true; + } + + return false; +} + +bool CppLocalRenaming::handlePaste() +{ + if (!isActive()) + return false; + + startRenameChange(); + m_editorWidget->BaseTextEditorWidget::paste(); + finishRenameChange(); + return true; +} + +bool CppLocalRenaming::handleCut() +{ + if (!isActive()) + return false; + + startRenameChange(); + m_editorWidget->BaseTextEditorWidget::paste(); + finishRenameChange(); + return true; +} + +bool CppLocalRenaming::handleSelectAll() +{ + if (!isActive()) + return false; + + QTextCursor cursor = m_editorWidget->textCursor(); + if (!isWithinRenameSelection(cursor.position())) + return false; + + modifyCursorSelection(cursor, renameSelectionBegin(), renameSelectionEnd()); + m_editorWidget->setTextCursor(cursor); + return true; +} + +bool CppLocalRenaming::isActive() const +{ + return m_renameSelectionIndex != -1; +} + +bool CppLocalRenaming::handleKeyPressEvent(QKeyEvent *e) +{ + if (!isActive()) + return false; + + QTextCursor cursor = m_editorWidget->textCursor(); + const int cursorPosition = cursor.position(); + const QTextCursor::MoveMode moveMode = (e->modifiers() & Qt::ShiftModifier) + ? QTextCursor::KeepAnchor + : QTextCursor::MoveAnchor; + + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Escape: + stop(); + e->accept(); + return true; + case Qt::Key_Home: { + // Send home to start of name when within the name and not at the start + if (renameSelectionBegin() < cursorPosition && cursorPosition <= renameSelectionEnd()) { + cursor.setPosition(renameSelectionBegin(), moveMode); + m_editorWidget->setTextCursor(cursor); + e->accept(); + return true; + } + break; + } + case Qt::Key_End: { + // Send end to end of name when within the name and not at the end + if (renameSelectionBegin() <= cursorPosition && cursorPosition < renameSelectionEnd()) { + cursor.setPosition(renameSelectionEnd(), moveMode); + m_editorWidget->setTextCursor(cursor); + e->accept(); + return true; + } + break; + } + case Qt::Key_Backspace: { + if (cursorPosition == renameSelectionBegin() && !cursor.hasSelection()) { + // Eat backspace at start of name when there is no selection + e->accept(); + return true; + } + break; + } + case Qt::Key_Delete: { + if (cursorPosition == renameSelectionEnd() && !cursor.hasSelection()) { + // Eat delete at end of name when there is no selection + e->accept(); + return true; + } + break; + } + default: { + break; + } + } // switch + + startRenameChange(); + + const bool wantEditBlock = isWithinRenameSelection(cursorPosition); + if (wantEditBlock) { + if (m_firstRenameChangeExpected) // Change inside rename selection + cursor.beginEditBlock(); + else + cursor.joinPreviousEditBlock(); + m_firstRenameChangeExpected = false; + } + emit processKeyPressNormally(e); + if (wantEditBlock) + cursor.endEditBlock(); + finishRenameChange(); + return true; +} + +QTextEdit::ExtraSelection &CppLocalRenaming::renameSelection() +{ + return m_selections[m_renameSelectionIndex]; +} + +void CppLocalRenaming::updateRenamingSelectionCursor(const QTextCursor &cursor) +{ + QTC_ASSERT(isActive(), return); + renameSelection().cursor = cursor; +} + +void CppLocalRenaming::updateRenamingSelectionFormat(const QTextCharFormat &format) +{ + QTC_ASSERT(isActive(), return); + renameSelection().format = format; +} + +void CppLocalRenaming::forgetRenamingSelection() +{ + m_renameSelectionIndex = -1; +} + +bool CppLocalRenaming::isWithinRenameSelection(int position) +{ + return renameSelectionBegin() <= position && position <= renameSelectionEnd(); +} + +bool CppLocalRenaming::findRenameSelection(int cursorPosition) +{ + for (int i = 0, total = m_selections.size(); i < total; ++i) { + const QTextEdit::ExtraSelection &sel = m_selections.at(i); + if (sel.cursor.position() <= cursorPosition && cursorPosition <= sel.cursor.anchor()) { + m_renameSelectionIndex = i; + return true; + } + } + + return false; +} + +void CppLocalRenaming::changeOtherSelectionsText(const QString &text) +{ + for (int i = 0, total = m_selections.size(); i < total; ++i) { + if (i == m_renameSelectionIndex) + continue; + + QTextEdit::ExtraSelection &selection = m_selections[i]; + const int pos = selection.cursor.selectionStart(); + selection.cursor.removeSelectedText(); + selection.cursor.insertText(text); + selection.cursor.setPosition(pos, QTextCursor::KeepAnchor); + } +} + +void CppLocalRenaming::onContentsChangeOfEditorWidgetDocument(int position, + int charsRemoved, + int charsAdded) +{ + Q_UNUSED(charsRemoved) + + if (!isActive() || m_modifyingSelections) + return; + + if (position + charsAdded == renameSelectionBegin()) // Insert at beginning, expand cursor + modifyCursorSelection(renameSelection().cursor, position, renameSelectionEnd()); + + // Keep in mind that cursor position and anchor move automatically + m_renameSelectionChanged = isWithinRenameSelection(position) + && isWithinRenameSelection(position + charsAdded); + + if (!m_renameSelectionChanged) + stop(); +} + +void CppLocalRenaming::startRenameChange() +{ + m_renameSelectionChanged = false; +} + +void CppLocalRenaming::updateEditorWidgetWithSelections() +{ + m_editorWidget->setExtraSelections(TextEditor::BaseTextEditorWidget::CodeSemanticsSelection, + m_selections); +} + +QTextCharFormat CppLocalRenaming::textCharFormat(TextEditor::TextStyle category) const +{ + return m_editorWidget->baseTextDocument()->fontSettings().toTextCharFormat(category); +} + +void CppLocalRenaming::finishRenameChange() +{ + if (!m_renameSelectionChanged) + return; + + m_modifyingSelections = true; + + QTextCursor cursor = m_editorWidget->textCursor(); + cursor.joinPreviousEditBlock(); + + modifyCursorSelection(cursor, renameSelectionBegin(), renameSelectionEnd()); + updateRenamingSelectionCursor(cursor); + changeOtherSelectionsText(cursor.selectedText()); + updateEditorWidgetWithSelections(); + + cursor.endEditBlock(); + + m_modifyingSelections = false; +} + +void CppLocalRenaming::stop() +{ + if (!isActive()) + return; + + updateRenamingSelectionFormat(textCharFormat(TextEditor::C_OCCURRENCES)); + updateEditorWidgetWithSelections(); + forgetRenamingSelection(); + + emit finished(); +} + +} // namespace Internal +} // namespace CppEditor diff --git a/src/plugins/cppeditor/cpplocalrenaming.h b/src/plugins/cppeditor/cpplocalrenaming.h new file mode 100644 index 00000000000..0e246321eda --- /dev/null +++ b/src/plugins/cppeditor/cpplocalrenaming.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef CPPLOCALRENAMING +#define CPPLOCALRENAMING + +#include + +#include + +namespace TextEditor { class BaseTextEditorWidget; } + +namespace CppEditor { +namespace Internal { + +class CppLocalRenaming : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(CppLocalRenaming) + +public: + explicit CppLocalRenaming(TextEditor::BaseTextEditorWidget *editorWidget); + + void updateLocalUseSelections(const QList &selections); + + bool start(); + bool isActive() const; + void stop(); + + // Delegates for the editor widget + bool handlePaste(); + bool handleCut(); + bool handleSelectAll(); + + // E.g. limit navigation keys to selection, stop() on Esc/Return or delegate + // to BaseTextEditorWidget::keyPressEvent() + bool handleKeyPressEvent(QKeyEvent *e); + +signals: + void finished(); + void processKeyPressNormally(QKeyEvent *e); + +private slots: + void onContentsChangeOfEditorWidgetDocument(int position, int charsRemoved, int charsAdded); + +private: + CppLocalRenaming(); + + // The "rename selection" is the local use selection on which the user started the renaming + bool findRenameSelection(int cursorPosition); + void forgetRenamingSelection(); + bool isWithinRenameSelection(int position); + + QTextEdit::ExtraSelection &renameSelection(); + int renameSelectionBegin() { return renameSelection().cursor.position(); } + int renameSelectionEnd() { return renameSelection().cursor.anchor(); } + + void updateRenamingSelectionCursor(const QTextCursor &cursor); + void updateRenamingSelectionFormat(const QTextCharFormat &format); + + void changeOtherSelectionsText(const QString &text); + + void startRenameChange(); + void finishRenameChange(); + + void updateEditorWidgetWithSelections(); + + QTextCharFormat textCharFormat(TextEditor::TextStyle category) const; + +private: + TextEditor::BaseTextEditorWidget *m_editorWidget; + + QList m_selections; + int m_renameSelectionIndex; + bool m_modifyingSelections; + bool m_renameSelectionChanged; + bool m_firstRenameChangeExpected; +}; + +} // namespace Internal +} // namespace CppEditor + +#endif // CPPLOCALRENAMING