diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 5b1f0f6d7ac..7a07837de12 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -256,6 +256,8 @@ public: void print(QPrinter *printer); void maybeSelectLine(); + void duplicateSelection(bool comment); + void duplicateBlockSelection(bool comment); void updateCannotDecodeInfo(); void collectToCircularClipboard(); @@ -6165,6 +6167,128 @@ void TextEditorWidget::copyLine() copy(); } +void TextEditorWidgetPrivate::duplicateSelection(bool comment) +{ + if (m_inBlockSelectionMode) { + duplicateBlockSelection(comment); + return; + } + + QTextCursor cursor = q->textCursor(); + + if (cursor.hasSelection()) { + + // Cannot "duplicate and comment" files without multi-line comment + if (comment && !m_commentDefinition.hasMultiLineStyle()) + return; + + QString dupText = cursor.selectedText().replace(QChar::ParagraphSeparator, QLatin1Char('\n')); + if (comment) + dupText = (m_commentDefinition.multiLineStart + dupText + m_commentDefinition.multiLineEnd); + const int selStart = cursor.selectionStart(); + const int selEnd = cursor.selectionEnd(); + const bool cursorAtStart = (cursor.position() == selStart); + cursor.setPosition(selEnd); + cursor.insertText(dupText); + cursor.setPosition(cursorAtStart ? selEnd : selStart); + cursor.setPosition(cursorAtStart ? selStart : selEnd, QTextCursor::KeepAnchor); + } else { + const int curPos = cursor.position(); + const QTextBlock &block = cursor.block(); + QString dupText = block.text() + QLatin1Char('\n'); + if (comment && m_commentDefinition.hasSingleLineStyle()) + dupText.append(m_commentDefinition.singleLine); + cursor.setPosition(block.position()); + cursor.insertText(dupText); + cursor.setPosition(curPos); + } + q->setTextCursor(cursor); +} + +void TextEditorWidgetPrivate::duplicateBlockSelection(bool comment) +{ + QTextCursor cursor = q->textCursor(); + + const TextBlockSelection curSel = m_blockSelection; + + if (curSel.positionColumn == curSel.anchorColumn) { + // No columns selected, duplicating multiple lines + + const bool isUp = curSel.positionBlock > curSel.anchorBlock; + const QString commentText = + (comment && m_commentDefinition.hasSingleLineStyle()) ? + m_commentDefinition.singleLine : QString(); + + QTextBlock block = cursor.block(); + QString dupText = commentText + block.text() + QLatin1Char('\n'); + + for (int b = curSel.firstBlockNumber(); b < curSel.lastBlockNumber(); ++b) { + if (isUp) { + block = block.previous(); + dupText.prepend(commentText + block.text() + QLatin1Char('\n')); + } else { + block = block.next(); + dupText.append(commentText + block.text() + QLatin1Char('\n')); + } + } + + if (isUp) + block = cursor.block(); + + cursor.setPosition(block.position() + block.length()); + cursor.insertText(dupText); + + } else { + // Duplicating full block selection with columns + + // Cannot "duplicate and comment" files without multi-line comment + if (comment && !m_commentDefinition.hasMultiLineStyle()) + return; + + const int fc = curSel.firstVisualColumn(); + const int lc = curSel.lastVisualColumn(); + const int l = lc - fc; + + cursor.beginEditBlock(); + for (int b = curSel.firstBlockNumber(); b <= curSel.lastBlockNumber(); ++b) { + const QTextBlock &block = m_document->document()->findBlockByNumber(b); + QString dupText = block.text(); + const int dupTextLength = dupText.length(); + + if (dupTextLength < lc) { + const QString addSpace(lc - dupTextLength, ' '); + cursor.setPosition(block.position() + dupTextLength); + cursor.insertText(addSpace); + dupText.append(addSpace); + } + + cursor.setPosition(block.position() + lc); + dupText = dupText.mid(fc, l); + + if (comment) + dupText = (m_commentDefinition.multiLineStart + dupText + m_commentDefinition.multiLineEnd); + cursor.insertText(dupText); + } + cursor.endEditBlock(); + } + + enableBlockSelection(curSel.positionBlock, curSel.positionColumn, + curSel.anchorBlock, curSel.anchorColumn); + + cursor = m_blockSelection.cursor(m_document.data()); + q->doSetTextCursor(cursor, m_blockSelection.hasSelection()); +} + +void TextEditorWidget::duplicateSelection() +{ + d->duplicateSelection(false); +} + +void TextEditorWidget::duplicateSelectionAndComment() +{ + d->duplicateSelection(true); +} + void TextEditorWidget::deleteLine() { d->maybeSelectLine(); diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 2cd60da3806..964c4d09c97 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -357,6 +357,8 @@ public: void cutLine(); void copyLine(); + void duplicateSelection(); + void duplicateSelectionAndComment(); void deleteLine(); void deleteEndOfWord(); void deleteEndOfWordCamelCase(); diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp index 81472b6eeae..bbd14f9efdd 100644 --- a/src/plugins/texteditor/texteditoractionhandler.cpp +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -142,6 +142,8 @@ public: QAction *m_unfoldAction; QAction *m_cutLineAction; QAction *m_copyLineAction; + QAction *m_duplicateSelectionAction; + QAction *m_duplicateSelectionAndCommentAction; QAction *m_deleteLineAction; QAction *m_deleteEndOfWordAction; QAction *m_deleteEndOfWordCamelCaseAction; @@ -206,6 +208,8 @@ TextEditorActionHandlerPrivate::TextEditorActionHandlerPrivate m_unfoldAction(0), m_cutLineAction(0), m_copyLineAction(0), + m_duplicateSelectionAction(0), + m_duplicateSelectionAndCommentAction(0), m_deleteLineAction(0), m_deleteEndOfWordAction(0), m_deleteEndOfWordCamelCaseAction(0), @@ -403,6 +407,14 @@ void TextEditorActionHandlerPrivate::createActions() [this] (TextEditorWidget *w) { w->copyLine(); }, false, tr("Copy &Line"), QKeySequence(tr("Ctrl+Ins")), G_EDIT_TEXT, advancedEditMenu); + m_duplicateSelectionAction = registerAction(DUPLICATE_SELECTION, + [this] (TextEditorWidget *w) { w->duplicateSelection(); }, false, tr("&Duplicate Selection"), + QKeySequence(), + G_EDIT_TEXT, advancedEditMenu); + m_duplicateSelectionAndCommentAction = registerAction(DUPLICATE_SELECTION_AND_COMMENT, + [this] (TextEditorWidget *w) { w->duplicateSelectionAndComment(); }, false, tr("&Duplicate Selection and Comment"), + QKeySequence(), + G_EDIT_TEXT, advancedEditMenu); m_upperCaseSelectionAction = registerAction(UPPERCASE_SELECTION, [this] (TextEditorWidget *w) { w->uppercaseSelection(); }, true, tr("Uppercase Selection"), QKeySequence(Core::UseMacShortcuts ? tr("Meta+Shift+U") : tr("Alt+Shift+U")), diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index 8dbaf93de4f..9b392e39807 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -144,6 +144,8 @@ const char UPPERCASE_SELECTION[] = "TextEditor.UppercaseSelection"; const char LOWERCASE_SELECTION[] = "TextEditor.LowercaseSelection"; const char CUT_LINE[] = "TextEditor.CutLine"; const char COPY_LINE[] = "TextEditor.CopyLine"; +const char DUPLICATE_SELECTION[] = "TextEditor.DuplicateSelection"; +const char DUPLICATE_SELECTION_AND_COMMENT[] = "TextEditor.DuplicateSelectionAndComment"; const char DELETE_LINE[] = "TextEditor.DeleteLine"; const char DELETE_END_OF_WORD[] = "TextEditor.DeleteEndOfWord"; const char DELETE_END_OF_WORD_CAMEL_CASE[] = "TextEditor.DeleteEndOfWordCamelCase";