// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cpplocalrenaming.h" #include #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::TextEditorWidget *editorWidget) : m_editorWidget(editorWidget) , m_modifyingSelections(false) , m_renameSelectionChanged(false) , m_firstRenameChangeExpected(false) { forgetRenamingSelection(); } void CppLocalRenaming::updateSelectionsForVariableUnderCursor( const QList &selections) { if (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(); emit started(); return true; } return false; } bool CppLocalRenaming::handlePaste() { if (!isActive()) return false; startRenameChange(); m_editorWidget->TextEditorWidget::paste(); finishRenameChange(); return true; } bool CppLocalRenaming::handleCut() { if (!isActive()) return false; startRenameChange(); m_editorWidget->TextEditorWidget::cut(); 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); const int undoSizeBeforeEdit = m_editorWidget->document()->availableUndoSteps(); if (wantEditBlock) { if (m_firstRenameChangeExpected) // Change inside rename selection cursor.beginEditBlock(); else cursor.joinPreviousEditBlock(); } emit processKeyPressNormally(e); if (wantEditBlock) { cursor.endEditBlock(); if (m_firstRenameChangeExpected // QTCREATORBUG-16350 && m_editorWidget->document()->availableUndoSteps() != undoSizeBeforeEdit) { m_firstRenameChangeExpected = false; } } finishRenameChange(); return true; } bool CppLocalRenaming::encourageApply() { if (!isActive()) return false; 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::isWithinSelection(const QTextEdit::ExtraSelection &selection, int position) { return selection.cursor.selectionStart() <= position && position <= selection.cursor.selectionEnd(); } bool CppLocalRenaming::isWithinRenameSelection(int position) { return isWithinSelection(renameSelection(), position); } bool CppLocalRenaming::isSameSelection(int cursorPosition) const { if (!isActive()) return false; const QTextEdit::ExtraSelection &sel = m_selections[m_renameSelectionIndex]; return isWithinSelection(sel, cursorPosition); } 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 (isWithinSelection(sel, cursorPosition)) { 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::TextEditorWidget::CodeSemanticsSelection, m_selections); } QTextCharFormat CppLocalRenaming::textCharFormat(TextEditor::TextStyle category) const { return m_editorWidget->textDocument()->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