From 2e93051aaf4aa84eae027e61d0b7c5f347ea9eba Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 23 Oct 2023 13:38:10 +0200 Subject: [PATCH] TextEditor: improve sort selected lines Try to get a sensible scope when there is no selection when sorting lines. Use the indent level of the current block and select all blocks that are not empty with the same indent level around that block before sorting the lines. Change-Id: I68cbd95f95a0cc4425a0339b992225c3946a6858 Reviewed-by: Christian Stenger Reviewed-by: Marcus Tillmanns --- src/plugins/texteditor/texteditor.cpp | 104 +++++++++++------- src/plugins/texteditor/texteditor.h | 2 +- .../texteditor/texteditoractionhandler.cpp | 4 +- src/plugins/texteditor/texteditorconstants.h | 2 +- tests/system/suite_tools/tst_sort/test.py | 2 +- 5 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index a667e29fbaa..39b38036055 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -668,8 +668,6 @@ public: void transformSelection(TransformationMethod method); - void transformSelectedLines(ListTransformationMethod method); - void slotUpdateExtraAreaWidth(std::optional width = {}); void slotUpdateRequest(const QRect &r, int dy); void slotUpdateBlockNotify(const QTextBlock &); @@ -2420,9 +2418,72 @@ void TextEditorWidget::lowercaseSelection() d->transformSelection([](const QString &str) { return str.toLower(); }); } -void TextEditorWidget::sortSelectedLines() +void TextEditorWidget::sortLines() { - d->transformSelectedLines([](QStringList &list) { list.sort(); }); + if (d->m_cursors.hasMultipleCursors()) + return; + + QTextCursor cursor = textCursor(); + if (!cursor.hasSelection()) { + // try to get a sensible scope for the sort + const QTextBlock currentBlock = cursor.block(); + QString text = currentBlock.text(); + if (text.simplified().isEmpty()) + return; + const TabSettings ts = textDocument()->tabSettings(); + const int currentIndent = ts.columnAt(text, TabSettings::firstNonSpace(text)); + + int anchor = currentBlock.position(); + for (auto block = currentBlock.previous(); block.isValid(); block = block.previous()) { + text = block.text(); + if (text.simplified().isEmpty() + || ts.columnAt(text, TabSettings::firstNonSpace(text)) != currentIndent) { + break; + } + anchor = block.position(); + } + + int pos = currentBlock.position(); + for (auto block = currentBlock.next(); block.isValid(); block = block.next()) { + text = block.text(); + if (text.simplified().isEmpty() + || ts.columnAt(text, TabSettings::firstNonSpace(text)) != currentIndent) { + break; + } + pos = block.position(); + } + if (anchor == pos) + return; + + cursor.setPosition(anchor); + cursor.setPosition(pos, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + } + + const bool downwardDirection = cursor.anchor() < cursor.position(); + int startPosition = cursor.selectionStart(); + int endPosition = cursor.selectionEnd(); + + cursor.setPosition(startPosition); + cursor.movePosition(QTextCursor::StartOfBlock); + startPosition = cursor.position(); + + cursor.setPosition(endPosition, QTextCursor::KeepAnchor); + if (cursor.positionInBlock() == 0) + cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + endPosition = qMax(cursor.position(), endPosition); + + const QString text = cursor.selectedText(); + QStringList lines = text.split(QChar::ParagraphSeparator); + lines.sort(); + cursor.insertText(lines.join(QChar::ParagraphSeparator)); + + // (re)select the changed lines + // Note: this assumes the transformation did not change the length + cursor.setPosition(downwardDirection ? startPosition : endPosition); + cursor.setPosition(downwardDirection ? endPosition : startPosition, QTextCursor::KeepAnchor); + setTextCursor(cursor); } void TextEditorWidget::indent() @@ -9039,41 +9100,6 @@ void TextEditorWidgetPrivate::transformSelection(TransformationMethod method) q->setMultiTextCursor(cursor); } -void TextEditorWidgetPrivate::transformSelectedLines(ListTransformationMethod method) -{ - if (!method || m_cursors.hasMultipleCursors()) - return; - - QTextCursor cursor = q->textCursor(); - if (!cursor.hasSelection()) - return; - - const bool downwardDirection = cursor.anchor() < cursor.position(); - int startPosition = cursor.selectionStart(); - int endPosition = cursor.selectionEnd(); - - cursor.setPosition(startPosition); - cursor.movePosition(QTextCursor::StartOfBlock); - startPosition = cursor.position(); - - cursor.setPosition(endPosition, QTextCursor::KeepAnchor); - if (cursor.positionInBlock() == 0) - cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::KeepAnchor); - cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); - endPosition = qMax(cursor.position(), endPosition); - - const QString text = cursor.selectedText(); - QStringList lines = text.split(QChar::ParagraphSeparator); - method(lines); - cursor.insertText(lines.join(QChar::ParagraphSeparator)); - - // (re)select the changed lines - // Note: this assumes the transformation did not change the length - cursor.setPosition(downwardDirection ? startPosition : endPosition); - cursor.setPosition(downwardDirection ? endPosition : startPosition, QTextCursor::KeepAnchor); - q->setTextCursor(cursor); -} - void TextEditorWidget::inSnippetMode(bool *active) { *active = d->m_snippetOverlay->isVisible(); diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 2af45826368..51f3cadec88 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -426,7 +426,7 @@ public: void uppercaseSelection(); void lowercaseSelection(); - void sortSelectedLines(); + void sortLines(); void cleanWhitespace(); diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp index 49b83b8a756..f16c94249bb 100644 --- a/src/plugins/texteditor/texteditoractionhandler.cpp +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -366,8 +366,8 @@ void TextEditorActionHandlerPrivate::createActions() [] (TextEditorWidget *w) { w->lowercaseSelection(); }, true, Tr::tr("Lowercase Selection"), QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+U") : Tr::tr("Alt+U")), G_EDIT_TEXT, advancedEditMenu); - m_modifyingActions << registerAction(SORT_SELECTED_LINES, - [] (TextEditorWidget *w) { w->sortSelectedLines(); }, false, Tr::tr("&Sort Selected Lines"), + m_modifyingActions << registerAction(SORT_LINES, + [] (TextEditorWidget *w) { w->sortLines(); }, false, Tr::tr("&Sort Lines"), QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+Shift+S") : Tr::tr("Alt+Shift+S")), G_EDIT_TEXT, advancedEditMenu); registerAction(FOLD, diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index 262ce6a5c45..e2115261882 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -157,7 +157,7 @@ const char INSERT_LINE_ABOVE[] = "TextEditor.InsertLineAboveCurrentLine"; const char INSERT_LINE_BELOW[] = "TextEditor.InsertLineBelowCurrentLine"; const char UPPERCASE_SELECTION[] = "TextEditor.UppercaseSelection"; const char LOWERCASE_SELECTION[] = "TextEditor.LowercaseSelection"; -const char SORT_SELECTED_LINES[] = "TextEditor.SortSelectedLines"; +const char SORT_LINES[] = "TextEditor.SortSelectedLines"; const char CUT_LINE[] = "TextEditor.CutLine"; const char COPY_LINE[] = "TextEditor.CopyLine"; const char ADD_SELECT_NEXT_FIND_MATCH[] = "TextEditor.AddSelectionNextFindMatch"; diff --git a/tests/system/suite_tools/tst_sort/test.py b/tests/system/suite_tools/tst_sort/test.py index a8eac9fa830..f2eed1eeae6 100644 --- a/tests/system/suite_tools/tst_sort/test.py +++ b/tests/system/suite_tools/tst_sort/test.py @@ -15,7 +15,7 @@ def main(): "visible='1' window=':Qt Creator_Core::Internal::MainWindow'}", 3000) placeCursorToLine(editor, "bbb") invokeMenuItem("Edit", "Select All") - invokeMenuItem("Edit", "Advanced", "Sort Selected Lines") + invokeMenuItem("Edit", "Advanced", "Sort Lines") test.verify(waitFor("str(editor.plainText) == sorted", 2000), "Verify that sorted text\n%s\nmatches the expected text\n%s" % (editor.plainText, sorted)) invokeMenuItem('File', 'Revert "unsorted.txt" to Saved')