From 5e4ed11e4ce2623267a4a930def8d12ba42e9c77 Mon Sep 17 00:00:00 2001 From: Wang Yu Date: Thu, 26 Feb 2026 10:14:39 +0800 Subject: [PATCH] fix: resolve multi-line unindent issue with Shift+Tab - Added UnindentTextCommand class to handle multi-line unindentation - Fixed unindentText() to detect and process multi-line selections - Added boundary checks to prevent position out of range warnings - Preserved text selection state after unindent operation Log: resolve multi-line unindent issue with Shift+Tab pms: BUG-351091 --- src/editor/dtextedit.cpp | 65 ++++++++++------ src/editor/indenttextcommond.cpp | 128 ++++++++++++++++++++++++++++++- src/editor/indenttextcommond.h | 30 +++++++- 3 files changed, 199 insertions(+), 24 deletions(-) diff --git a/src/editor/dtextedit.cpp b/src/editor/dtextedit.cpp index 7f74a08f4..f16871641 100644 --- a/src/editor/dtextedit.cpp +++ b/src/editor/dtextedit.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2011-2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2011 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -1602,30 +1602,51 @@ void TextEdit::unindentText() { qDebug() << "Unindenting text"; QTextCursor cursor = this->textCursor(); - cursor.movePosition(QTextCursor::StartOfBlock); - int pos = cursor.position(); - cursor.setPosition(cursor.position() + 1, QTextCursor::KeepAnchor); - //the text in front of current line is '\t'. - if ("\t" == cursor.selectedText()) { - qDebug() << "Unindenting text with tab"; - DeleteBackCommand *com = new DeleteBackCommand(cursor, this); + if (cursor.hasSelection()) { + qDebug() << "Unindenting text with selection"; + //calculate the start postion and the end postion of current selection. + int pos1 = cursor.position(); + int pos2 = cursor.anchor(); + if (pos1 > pos2) + std::swap(pos1, pos2); + + //calculate the start line and end line of current selection. + cursor.setPosition(pos1); + int line1 = cursor.blockNumber(); + cursor.setPosition(pos2); + int line2 = cursor.blockNumber(); + + //do the unindent operation for multiple lines + auto com = new UnindentTextCommand(this, pos1, pos2, line1, line2, m_tabSpaceNumber); m_pUndoStack->push(com); - } - //the text in front of current line is ' '. - else if (" " == cursor.selectedText()) { - qDebug() << "Unindenting text with space"; - int startpos = pos; - int cnt = 0; - // calculate the number of ' '. - while (document()->characterAt(pos) == ' ' && cnt < m_tabSpaceNumber) { - pos++; - cnt++; + } else { + //single line unindent (original behavior) + cursor.movePosition(QTextCursor::StartOfBlock); + int pos = cursor.position(); + cursor.setPosition(cursor.position() + 1, QTextCursor::KeepAnchor); + + //the text in front of current line is '\t'. + if ("\t" == cursor.selectedText()) { + qDebug() << "Unindenting text with tab"; + DeleteBackCommand *com = new DeleteBackCommand(cursor, this); + m_pUndoStack->push(com); + } + //the text in front of current line is ' '. + else if (" " == cursor.selectedText()) { + qDebug() << "Unindenting text with space"; + int startpos = pos; + int cnt = 0; + // calculate the number of ' '. + while (document()->characterAt(pos) == ' ' && cnt < m_tabSpaceNumber) { + pos++; + cnt++; + } + cursor.setPosition(startpos); + cursor.setPosition(pos, QTextCursor::KeepAnchor); + DeleteBackCommand *com = new DeleteBackCommand(cursor, this); + m_pUndoStack->push(com); } - cursor.setPosition(startpos); - cursor.setPosition(pos, QTextCursor::KeepAnchor); - DeleteBackCommand *com = new DeleteBackCommand(cursor, this); - m_pUndoStack->push(com); } qDebug() << "Unindenting text successfully"; } diff --git a/src/editor/indenttextcommond.cpp b/src/editor/indenttextcommond.cpp index 798656a93..3051cfc57 100644 --- a/src/editor/indenttextcommond.cpp +++ b/src/editor/indenttextcommond.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -86,3 +86,129 @@ void IndentTextCommand::undo() } qDebug() << "IndentTextCommand undo exit"; } + +UnindentTextCommand::UnindentTextCommand(TextEdit* edit,int startpos,int endpos,int startline,int endline,int tabSpaceNumber): + m_edit(edit), + m_startpos(startpos), + m_endpos(endpos), + m_startline(startline), + m_endline(endline), + m_tabSpaceNumber(tabSpaceNumber) +{ + qDebug() << "UnindentTextCommand created - startpos:" << startpos + << "endpos:" << endpos << "lines:" << startline << "-" << endline + << "hasSelection:" << m_edit->textCursor().hasSelection(); + m_hasselected = m_edit->textCursor().hasSelection(); + m_removedChars.resize(endline - startline + 1); +} + +UnindentTextCommand::~UnindentTextCommand() +{ + qDebug() << "UnindentTextCommand destroyed"; +} + +void UnindentTextCommand::redo() +{ + qInfo() << "UnindentTextCommand redo - removing indents from lines:" + << m_startline << "-" << m_endline; + auto cursor = m_edit->textCursor(); + + //remove indentation from multiple lines. + cursor.setPosition(m_startpos); + int totalRemoved = 0; + + for(int i=m_startline;i<=m_endline;i++){ + cursor.movePosition(QTextCursor::StartOfBlock); + int lineStart = cursor.position(); + int removed = 0; + + //check if line starts with tab + if(m_edit->document()->characterAt(lineStart) == '\t'){ + cursor.deleteChar(); + removed = 1; + } + //check if line starts with spaces + else if(m_edit->document()->characterAt(lineStart) == ' '){ + int cnt = 0; + int pos = lineStart; + while(m_edit->document()->characterAt(pos) == ' ' && cnt < m_tabSpaceNumber){ + pos++; + cnt++; + } + if(cnt > 0){ + cursor.setPosition(lineStart); + cursor.setPosition(pos, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + removed = cnt; + } + } + + m_removedChars[i - m_startline] = removed; + totalRemoved += removed; + cursor.movePosition(QTextCursor::NextBlock); + } + + //reset selection. + if(m_hasselected){ + qDebug() << "UnindentTextCommand redo, m_hasselected, totalRemoved:" << totalRemoved; + if(m_startline == m_endline){ + qDebug() << "UnindentTextCommand redo, m_startline == m_endline"; + int newStartPos = m_startpos - m_removedChars[0]; + if(newStartPos < 0) newStartPos = 0; + cursor.setPosition(newStartPos); + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::EndOfBlock,QTextCursor::KeepAnchor); + m_edit->setTextCursor(cursor); + } + else{ + qDebug() << "UnindentTextCommand redo, m_startline != m_endline"; + int firstLineRemoved = m_removedChars[0]; + int newStartPos = m_startpos - firstLineRemoved; + int newEndPos = m_endpos - totalRemoved; + + //ensure positions are valid + if(newStartPos < 0) newStartPos = 0; + if(newEndPos < newStartPos) newEndPos = newStartPos; + + cursor.setPosition(newStartPos); + cursor.setPosition(newEndPos, QTextCursor::KeepAnchor); + m_edit->setTextCursor(cursor); + } + } + qDebug() << "UnindentTextCommand redo exit"; +} + +void UnindentTextCommand::undo() +{ + qInfo() << "UnindentTextCommand undo - restoring indents to lines:" + << m_startline << "-" << m_endline; + auto cursor = m_edit->textCursor(); + + //restore indentation to multiple lines. + cursor.setPosition(m_startpos); + for(int i=m_startline;i<=m_endline;i++){ + qDebug() << "UnindentTextCommand undo, i:" << i; + cursor.movePosition(QTextCursor::StartOfBlock); + int removed = m_removedChars[i - m_startline]; + + if(removed == 1){ + //restore tab + cursor.insertText("\t"); + } + else if(removed > 1){ + //restore spaces + cursor.insertText(QString(removed, ' ')); + } + + cursor.movePosition(QTextCursor::NextBlock); + } + + //reset selection + if(m_hasselected){ + qDebug() << "UnindentTextCommand undo, m_hasselected"; + cursor.setPosition(m_startpos); + cursor.setPosition(m_endpos, QTextCursor::KeepAnchor); + m_edit->setTextCursor(cursor); + } + qDebug() << "UnindentTextCommand undo exit"; +} diff --git a/src/editor/indenttextcommond.h b/src/editor/indenttextcommond.h index 107304244..876184f25 100644 --- a/src/editor/indenttextcommond.h +++ b/src/editor/indenttextcommond.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -35,4 +35,32 @@ class IndentTextCommand:public QUndoCommand }; +//unindent text in front of multiple lines +class UnindentTextCommand:public QUndoCommand +{ +public: + UnindentTextCommand(TextEdit* edit,int startpos,int endpos,int startline,int endline,int tabSpaceNumber); + virtual ~UnindentTextCommand(); + + virtual void redo(); + virtual void undo(); + +private: + + TextEdit* m_edit=nullptr; + //the start postion of selected text. + int m_startpos=0; + //the end postion of selected text. + int m_endpos=0; + //the start line of selected text. + int m_startline=0; + //the end line of selected text. + int m_endline=0; + bool m_hasselected=false; + int m_tabSpaceNumber=4; + //store the number of characters removed from each line + QVector m_removedChars; + +}; + #endif // IndentTextCommand_H