From 67d7a97729331279ed9f86f2945916c2e2bbb4fc Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Fri, 13 Dec 2024 16:34:29 +0100 Subject: [PATCH] Improve indentation API (#231) * refactor: Simplify indenation implementation * feat: Add TextDocument::indentationAtLine * feat: TextDocument::setIndentation This function allows setting the *absolute* indentation for the current line/selection. * feat: Line-based indentation API Add API for indenting at a given line number, not just at the current selection/position. * refactor!: indentationAt API now returns int The `indentationAtLine` and `...AtPosition` methods now return `int`, in accordance to the `indent` and `setIndentation` methods. The old behavior is still accessible via the `indentTextAtLine` and `indentTextAtPosition` functions. It turned out to still be useful, as there are a few situation where you just want to copy the indentation from another line. Getting access to the original indentation is useful there, as it can deal better with cases where Knuts indentation settings don't match up with the actual indentation in the file. * refactor!: Remove TextDocument::removeIndent Calling `indent` with negative values has exactly the same effect, so remove this duplicate function. `remove` is also not a great name for what the function did, as it didn't remove the entire indentation but rather "reduced" it. * chore: Bump photonwidgets to new indentation API Note: This uses a yet-unmerged commit in photonwidgets Ideally this commit should be updated to a stable version of photonwidgets before merging * fix: Mark indentation getter API as Q_INVOKABLE Clazy in the CI is right, that these should indeed not be slots, but rather Q_INVOKABLE. --- .reuse/dep5 | 39 ---- 3rdparty-kdab/photonwidgets | 2 +- REUSE.toml | 46 ++++ docs/API/knut/textdocument.md | 62 +++++- src/core/cppdocument.cpp | 6 +- src/core/textdocument.cpp | 201 +++++++++++++----- src/core/textdocument.h | 11 +- src/core/textdocument_p.h | 2 +- test_data/projects/mfc-dialog/TutorialDlg.cpp | 2 +- .../indent/indent.txt.expected | 14 +- .../indent/indent.txt.original | 14 +- tests/tst_textdocument.cpp | 44 +++- 12 files changed, 327 insertions(+), 116 deletions(-) delete mode 100644 .reuse/dep5 create mode 100644 REUSE.toml diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index fb5b0aa9..00000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,39 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: knut -Upstream-Contact: -Source: https://github.com/KDAB/knut/ - -#misc config files -Files: .pre-commit-config.yaml .codespellrc .clangd **.clang-format .clazy .gitattributes .github/workflows/documentation.yml .gitignore .gitmodules .krazy knut.code-workspace .github/**.yml mkdocs.yml -Copyright: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company -License: BSD-3-Clause - -#misc documentation -Files: *.md docs/API/mapping.txt docs/**.png docs/**.puml docs/**.gif docs/**.svg docs/**.css -Copyright: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company -License: GPL-3.0-only - -#misc source code -Files: *.json *.ui *.qrc *.rc *.xml src/**.png src/**.ico version.txt -Copyright: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company -License: GPL-2.0-only OR GPL-3.0-only - -#test_data -Files: test_data/** -Copyright: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company -License: GPL-2.0-only OR GPL-3.0-only - -#3rdparty test_data -Files: test_data/rcfiles/cryEdit/Resource.h test_data/rcfiles/luaDebugger/resource.h -Copyright: Amazon.com, Inc. or its affiliates or its licensors -License: Apache-2.0 OR MIT - -#3rdparty from the Qt Company -Files: qt6.natvis -Copyright: 2022 The Qt Company Ltd. -License: GPL-3.0-only OR LicenseRef-Qt-Commercial - -#3rdparty Pictogrammers icons used inside the GUI application -Files: src/gui/gui/*.png -Copyright: The Pictogrammers -License: Apache-2.0 diff --git a/3rdparty-kdab/photonwidgets b/3rdparty-kdab/photonwidgets index 3ea1a69f..f6fe2078 160000 --- a/3rdparty-kdab/photonwidgets +++ b/3rdparty-kdab/photonwidgets @@ -1 +1 @@ -Subproject commit 3ea1a69f95a850a38e90de2d766aca846448b0f9 +Subproject commit f6fe2078c4dbdce1ca763470eaa8239b78b712a8 diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 00000000..53b589e7 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,46 @@ +version = 1 +SPDX-PackageName = "knut" +SPDX-PackageSupplier = "" +SPDX-PackageDownloadLocation = "https://github.com/KDAB/knut/" + +[[annotations]] +path = [".pre-commit-config.yaml", ".codespellrc", ".clangd", "**.clang-format", ".clazy", ".gitattributes", ".github/workflows/documentation.yml", ".gitignore", ".gitmodules", ".krazy", "knut.code-workspace", ".github/**.yml", "mkdocs.yml"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2024 Klarälvdalens Datakonsult AB, a KDAB Group company " +SPDX-License-Identifier = "BSD-3-Clause" + +[[annotations]] +path = ["**.md", "docs/API/mapping.txt", "docs/**.png", "docs/**.puml", "docs/**.gif", "docs/**.svg", "docs/**.css"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2024 Klarälvdalens Datakonsult AB, a KDAB Group company " +SPDX-License-Identifier = "GPL-3.0-only" + +[[annotations]] +path = ["**.json", "**.ui", "**.qrc", "**.rc", "**.xml", "src/**.png", "src/**.ico", "version.txt"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2024 Klarälvdalens Datakonsult AB, a KDAB Group company " +SPDX-License-Identifier = "GPL-2.0-only OR GPL-3.0-only" + +[[annotations]] +path = "test_data/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2024 Klarälvdalens Datakonsult AB, a KDAB Group company " +SPDX-License-Identifier = "GPL-2.0-only OR GPL-3.0-only" + +[[annotations]] +path = ["test_data/rcfiles/cryEdit/Resource.h", "test_data/rcfiles/luaDebugger/resource.h"] +precedence = "aggregate" +SPDX-FileCopyrightText = "Amazon.com, Inc. or its affiliates or its licensors" +SPDX-License-Identifier = "Apache-2.0 OR MIT" + +[[annotations]] +path = "qt6.natvis" +precedence = "aggregate" +SPDX-FileCopyrightText = "2022 The Qt Company Ltd." +SPDX-License-Identifier = "GPL-3.0-only OR LicenseRef-Qt-Commercial" + +[[annotations]] +path = "src/gui/gui/**.png" +precedence = "aggregate" +SPDX-FileCopyrightText = "The Pictogrammers" +SPDX-License-Identifier = "Apache-2.0" diff --git a/docs/API/knut/textdocument.md b/docs/API/knut/textdocument.md index a85cfcd1..131d0f89 100644 --- a/docs/API/knut/textdocument.md +++ b/docs/API/knut/textdocument.md @@ -62,7 +62,11 @@ Inherited properties: [Document properties](../knut/document.md#properties) ||**[gotoStartOfWord](#gotoStartOfWord)**()| |bool |**[hasSelection](#hasSelection)**()| ||**[indent](#indent)**(int count)| -||**[indentationAtPosition](#indentationAtPosition)**(int pos)| +||**[indentLine](#indentLine)**(int count, int line)| +|string |**[indentTextAtLine](#indentTextAtLine)**(int line = -1)| +|string |**[indentTextAtPosition](#indentTextAtPosition)**(int pos)| +|int |**[indentationAtLine](#indentationAtLine)**(int line = -1)| +|int |**[indentationAtPosition](#indentationAtPosition)**(int pos)| ||**[insert](#insert)**(string text)| ||**[insertAtLine](#insertAtLine)**(string text, int line = -1)| ||**[insertAtPosition](#insertAtPosition)**(string text, int pos)| @@ -72,7 +76,6 @@ Inherited properties: [Document properties](../knut/document.md#properties) ||**[positionAt](#positionAt)**(int line, int col)| ||**[redo](#redo)**(int count)| ||**[remove](#remove)**(int length)| -||**[removeIndent](#removeIndent)**(int count)| ||**[replace](#replace)**(int length, string text)| ||**[replace](#replace)**([RangeMark](../knut/rangemark.md) range, string text)| ||**[replace](#replace)**(int from, int to, string text)| @@ -96,6 +99,8 @@ Inherited properties: [Document properties](../knut/document.md#properties) ||**[selectStartOfWord](#selectStartOfWord)**()| ||**[selectTo](#selectTo)**(int pos)| ||**[selectToMark](#selectToMark)**([Mark](../knut/mark.md) mark)| +||**[setIndentation](#setIndentation)**(int indent)| +||**[setIndentationAtLine](#setIndentationAtLine)**(int indent, int line)| ||**[undo](#undo)**(int count)| ||**[unselect](#unselect)**()| @@ -309,9 +314,41 @@ Returns true if the editor has a selection. Indents the current line `count` times. If there's a selection, indent all lines in the selection. -#### **indentationAtPosition**(int pos) +The `count` can be negative to reduce the existing indentation. -Returns the indentation at the given position. +See also: [`setIndentation`](#setIndentation). + +#### **indentLine**(int count, int line) + +Indents the `line` `count` times. + +See also: [`indent`](#indent) + +#### string **indentTextAtLine**(int line = -1) + +Returns the indentation text at the given line. + +If `line` is -1 it will return the indentation at the current line. +If `line` is larger than the number of lines in the document, it will return an empty string + +Note: To get the level of indentation, use [`indentationAtLine`](#indentationAtLine). + +#### string **indentTextAtPosition**(int pos) + +Returns the indentation text at the given position. + +Note: To get the level of indentation, use [`indentationAtPosition`](#indentationAtPosition). + +#### int **indentationAtLine**(int line = -1) + +Returns the indentation level at the given line. + +If `line` is -1 it will return the indentation at the current line. +If `line` is larger than the number of lines in the document, it will return 0 + +#### int **indentationAtPosition**(int pos) + +Returns the indentation level at the given position. #### **insert**(string text) @@ -355,10 +392,6 @@ Redo `count` times the last actions. Remove `length` character from the current position. -#### **removeIndent**(int count) - -Indents the current line `count` times. If there's a selection, indent all lines in the selection. - #### **replace**(int length, string text) Replaces `length` characters from the current position with the string `text`. @@ -495,6 +528,19 @@ Selects the text from the current position to `pos`. Selects the text from the cursor position to the `mark`. +#### **setIndentation**(int indent) + +Sets the absolute indentation of the current line to `indent` indentations. +If there's a selection, sets the indentation of all lines in the selection. + +For relative indentation, see [`indent`](#indent) and [`indentLine`](#indentLine). + +#### **setIndentationAtLine**(int indent, int line) + +Sets the absolute indentation of the `line` to `indent` indentations. + +See also: [`setIndentation`](#setIndentation) + #### **undo**(int count) Undo `count` times the last actions. diff --git a/src/core/cppdocument.cpp b/src/core/cppdocument.cpp index b5662c78..046dfe01 100644 --- a/src/core/cppdocument.cpp +++ b/src/core/cppdocument.cpp @@ -1339,12 +1339,12 @@ CppDocument::addMemberOrMethod(const QString &memberInfo, const QString &classNa const auto fields = match.getAll("field"); if (!fields.isEmpty()) { const auto &pos = fields.last(); - const auto indent = indentationAtPosition(pos.end()); + const auto indent = indentTextAtPosition(pos.end()); insertAtPosition("\n" + indent + memberText, pos.end()); } else { const auto access = match.getAll("access"); const auto &pos = access.last(); - const auto indent = indentationAtPosition(pos.end()); + const auto indent = indentTextAtPosition(pos.end()); insertAtPosition("\n" + indent + memberText, pos.end()); } } else { @@ -1519,7 +1519,7 @@ bool CppDocument::addSpecifierSection(const QString &memberText, const QString & if (!result.isEmpty()) { const auto &match = result.last(); const auto pos = match.get("pos"); - const auto indent = indentationAtPosition(pos.end()); + const auto indent = indentTextAtPosition(pos.end()); const QString newSpecifier = QString("\n\n%1:").arg(accessSpecifierMap.value(specifier)); diff --git a/src/core/textdocument.cpp b/src/core/textdocument.cpp index d363ff1f..f4768cb3 100644 --- a/src/core/textdocument.cpp +++ b/src/core/textdocument.cpp @@ -194,7 +194,7 @@ bool TextDocument::eventFilter(QObject *watched, QEvent *event) else if (keyEvent == QKeySequence::MoveToPreviousPage) return false; else if ((keyEvent->key() == Qt::Key_Backtab)) - removeIndent(); + indent(-1); else if (keyEvent->key() == Qt::Key_Tab) indent(); else if (keyEvent == QKeySequence::Undo) @@ -1534,7 +1534,12 @@ static int firstNonSpace(const QString &text) return i; } -static int indentOneLine(QTextCursor &cursor, int tabCount, const TabSettings &settings) +static QString indentToString(int indentSize, const TabSettings &settings) +{ + return settings.insertSpaces ? QString(indentSize * settings.tabSize, ' ') : QString(indentSize, '\t'); +} + +static int indentOneLine(QTextCursor &cursor, int tabCount, const TabSettings &settings, bool relative) { QString text = cursor.selectedText(); cursor.removeSelectedText(); @@ -1542,83 +1547,118 @@ static int indentOneLine(QTextCursor &cursor, int tabCount, const TabSettings &s const int oldSize = text.size(); const int firstChar = firstNonSpace(text); const int startColumn = columnAt(text, firstChar, settings.tabSize); - const int indentSize = qMax(startColumn / settings.tabSize + tabCount, 0); + const int currentIndent = startColumn / settings.tabSize; + const int indentSize = qMax(relative ? (currentIndent + tabCount) : tabCount, 0); text.remove(0, firstChar); - if (settings.insertSpaces) - text = QString(indentSize * settings.tabSize, ' ') + text; - else - text = QString(indentSize, '\t') + text; + text = indentToString(indentSize, settings) + text; cursor.insertText(text); return text.size() - oldSize; } -void indentTextInTextEdit(QPlainTextEdit *textEdit, int tabCount) +static void indentBlocksInTextEdit(QPlainTextEdit *textEdit, int blockStart, int blockEnd, int tabCount, bool relative) { const auto settings = Core::Settings::instance()->value(Core::Settings::Tab); - QTextCursor cursor = textEdit->textCursor(); - const bool hasSelection = cursor.hasSelection(); - const int lineStart = textEdit->document()->findBlock(cursor.selectionStart()).blockNumber(); - const int lineEnd = textEdit->document()->findBlock(cursor.selectionEnd()).blockNumber(); + + // Make sure we don't move the cursor outside the first line it started on. + const int minStart = textEdit->document()->findBlock(cursor.selectionStart()).position(); + int newStart = cursor.selectionStart(); + int newEnd = cursor.selectionEnd(); // Move the position to the beginning of the first line - int startPosition = cursor.position(); - cursor.setPosition(cursor.selectionStart()); + cursor.setPosition(textEdit->document()->findBlockByNumber(blockStart).position()); cursor.beginEditBlock(); - if (hasSelection) { - startPosition = cursor.position(); - // Iterate through all line, and change the indentation - for (int line = lineStart; line <= lineEnd; ++line) { - cursor.select(QTextCursor::LineUnderCursor); - const int delta = indentOneLine(cursor, tabCount, settings); - if (line == lineStart) - startPosition += delta; - cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor); - } - const int endPosition = cursor.position(); - // Select whole the lines indented - cursor.setPosition(startPosition); - cursor.setPosition(endPosition - 1, QTextCursor::KeepAnchor); - } else { + // Iterate through all line, and change the indentation + for (int block = blockStart; block <= blockEnd; ++block) { cursor.select(QTextCursor::LineUnderCursor); - startPosition += indentOneLine(cursor, tabCount, settings); - const int finalLine = textEdit->document()->findBlock(startPosition).blockNumber(); - if (finalLine != lineStart) - gotoLineInTextEdit(textEdit, lineStart + 1); - else - cursor.setPosition(startPosition); + // We need to update the new selection if we're doing modifications at or before the selection. + const auto updateStart = cursor.selectionStart() <= newStart; + const auto updateEnd = cursor.selectionStart() <= newEnd; + + const int delta = indentOneLine(cursor, tabCount, settings, relative); + + // update the position of the selection, depending on how much text was added/removed + if (updateStart) { + newStart += delta; + } + if (updateEnd) { + newEnd += delta; + } + cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor); } cursor.endEditBlock(); + + // Restore the selection, adjusted for the inserted/removed indentation + cursor.setPosition(qMax(minStart, newStart)); + cursor.setPosition(qMax(minStart, newEnd), QTextCursor::KeepAnchor); + textEdit->setTextCursor(cursor); } +void indentTextInTextEdit(QPlainTextEdit *textEdit, int tabCount, bool relative) +{ + QTextCursor cursor = textEdit->textCursor(); + const int blockStart = textEdit->document()->findBlock(cursor.selectionStart()).blockNumber(); + const int blockEnd = textEdit->document()->findBlock(cursor.selectionEnd()).blockNumber(); + + indentBlocksInTextEdit(textEdit, blockStart, blockEnd, tabCount, relative); +} + /*! * \qmlmethod TextDocument::indent(int count) * Indents the current line `count` times. If there's a selection, indent all lines in the selection. + * + * The `count` can be negative to reduce the existing indentation. + * + * See also: [`setIndentation`](#setIndentation). */ void TextDocument::indent(int count) { LOG_AND_MERGE(count); - while (count != 0) { - indentTextInTextEdit(m_document, 1); - --count; - } + indentTextInTextEdit(m_document, count); } /*! - * \qmlmethod TextDocument::removeIndent(int count) - * Indents the current line `count` times. If there's a selection, indent all lines in the selection. + * \qmlmethod TextDocument::indentLine(int count, int line) + * Indents the `line` `count` times. + * + * See also: [`indent`](#indent) */ -void TextDocument::removeIndent(int count) +void TextDocument::indentLine(int count, int line) { - LOG_AND_MERGE(count); - while (count != 0) { - indentTextInTextEdit(m_document, -1); - --count; - } + LOG(LOG_ARG("count", count), LOG_ARG("line", line)); + + indentBlocksInTextEdit(m_document, line - 1, line - 1, count, true); +} + +/*! + * \qmlmethod TextDocument::setIndentation(int indent) + * Sets the absolute indentation of the current line to `indent` indentations. + * If there's a selection, sets the indentation of all lines in the selection. + * + * For relative indentation, see [`indent`](#indent) and [`indentLine`](#indentLine). + */ +void TextDocument::setIndentation(int indent) +{ + LOG(LOG_ARG("indent", indent)); + + indentTextInTextEdit(m_document, indent, false); +} + +/*! + * \qmlmethod TextDocument::setIndentationAtLine(int indent, int line) + * Sets the absolute indentation of the `line` to `indent` indentations. + * + * See also: [`setIndentation`](#setIndentation) + */ +void TextDocument::setIndentationAtLine(int indent, int line) +{ + LOG(LOG_ARG("indent", indent), LOG_ARG("line", line)); + + indentBlocksInTextEdit(m_document, line - 1, line - 1, indent, false); } void TextDocument::setLineEnding(LineEnding newLineEnding) @@ -1632,12 +1672,15 @@ void TextDocument::setLineEnding(LineEnding newLineEnding) } /*! - * \qmlmethod TextDocument::indentationAtPosition(int pos) - * Returns the indentation at the given position. + * \qmlmethod string TextDocument::indentTextAtPosition(int pos) + * Returns the indentation text at the given position. + * + * Note: To get the level of indentation, use [`indentationAtPosition`](#indentationAtPosition). */ -QString TextDocument::indentationAtPosition(int pos) +QString TextDocument::indentTextAtPosition(int pos) const { - LOG(pos); + LOG(LOG_ARG("position", pos)); + auto cursor = m_document->textCursor(); cursor.setPosition(pos); cursor.movePosition(QTextCursor::StartOfLine); @@ -1646,4 +1689,60 @@ QString TextDocument::indentationAtPosition(int pos) return line.left(line.indexOf(nonSpaces)); } +/*! + * \qmlmethod string TextDocument::indentTextAtLine(int line = -1) + * Returns the indentation text at the given line. + * + * If `line` is -1 it will return the indentation at the current line. + * If `line` is larger than the number of lines in the document, it will return an empty string + * + * Note: To get the level of indentation, use [`indentationAtLine`](#indentationAtLine). + */ +QString TextDocument::indentTextAtLine(int line /* = -1 */) const +{ + LOG(LOG_ARG("line", line)); + + if (line <= 0) { + line = this->line(); + } + + // API-wise the line numbers are 1-based, but internally they are 0-based + auto blockNumber = line - 1; + + const QTextBlock &block = m_document->document()->findBlockByNumber(blockNumber); + if (block.isValid()) { + return indentTextAtPosition(block.position()); + } + return 0; +} + +/*! + * \qmlmethod int TextDocument::indentationAtPosition(int pos) + * Returns the indentation level at the given position. + */ +int TextDocument::indentationAtPosition(int pos) const +{ + LOG(LOG_ARG("position", pos)); + + const auto indentText = indentTextAtPosition(pos); + const auto settings = Core::Settings::instance()->value(Core::Settings::Tab); + return columnAt(indentText, indentText.size(), settings.tabSize) / settings.tabSize; +} + +/*! + * \qmlmethod int TextDocument::indentationAtLine(int line = -1) + * Returns the indentation level at the given line. + * + * If `line` is -1 it will return the indentation at the current line. + * If `line` is larger than the number of lines in the document, it will return 0 + */ +int TextDocument::indentationAtLine(int line /* = -1 */) const +{ + LOG(LOG_ARG("line", line)); + + const auto indentText = indentTextAtLine(line); + const auto settings = Core::Settings::instance()->value(Core::Settings::Tab); + return columnAt(indentText, indentText.size(), settings.tabSize) / settings.tabSize; +} + } // namespace Core diff --git a/src/core/textdocument.h b/src/core/textdocument.h index af7f3518..e88f3662 100644 --- a/src/core/textdocument.h +++ b/src/core/textdocument.h @@ -189,8 +189,15 @@ public slots: // Indentation void indent(int count = 1); - void removeIndent(int count = 1); - QString indentationAtPosition(int pos); + void indentLine(int count, int line); + void setIndentation(int indent); + void setIndentationAtLine(int indent, int line); + +public: + Q_INVOKABLE int indentationAtPosition(int pos) const; + Q_INVOKABLE int indentationAtLine(int line = -1) const; + Q_INVOKABLE QString indentTextAtPosition(int pos) const; + Q_INVOKABLE QString indentTextAtLine(int line = -1) const; signals: void positionChanged(); diff --git a/src/core/textdocument_p.h b/src/core/textdocument_p.h index 304c176c..73a0dd85 100644 --- a/src/core/textdocument_p.h +++ b/src/core/textdocument_p.h @@ -25,7 +25,7 @@ struct TabSettings NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(TabSettings, insertSpaces, tabSize); -void indentTextInTextEdit(QPlainTextEdit *textEdit, int tabCount); +void indentTextInTextEdit(QPlainTextEdit *textEdit, int tabCount, bool relative = true); void gotoLineInTextEdit(QPlainTextEdit *textEdit, int line, int column = 1); } // namespace Core diff --git a/test_data/projects/mfc-dialog/TutorialDlg.cpp b/test_data/projects/mfc-dialog/TutorialDlg.cpp index 1c7fc39c..50ca6095 100644 --- a/test_data/projects/mfc-dialog/TutorialDlg.cpp +++ b/test_data/projects/mfc-dialog/TutorialDlg.cpp @@ -18,7 +18,7 @@ CTutorialDlg::CTutorialDlg(CWnd* pParent /*=nullptr*/) - : CDialogEx(IDD_TUTORIAL_DIALOG, pParent) + : CDialogEx(IDD_TUTORIAL_DIALOG, pParent) , m_check(FALSE) , m_message(_T("")) { diff --git a/test_data/tst_textdocument/indent/indent.txt.expected b/test_data/tst_textdocument/indent/indent.txt.expected index 464a7898..2d7e8c87 100644 --- a/test_data/tst_textdocument/indent/indent.txt.expected +++ b/test_data/tst_textdocument/indent/indent.txt.expected @@ -1,4 +1,4 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque convallis ipsum ac odio aliquet tincidunt. Mauris ut magna vitae mauris fringilla condimentum. @@ -16,8 +16,14 @@ Quisque convallis ipsum ac odio aliquet tincidunt. Mauris ut magna vitae mauris fringilla condimentum. Proin non mi placerat, ultricies diam sit amet, ultricies nisi. -Lorem ipsum dolor sit amet, consectetur adipiscing elit. -Quisque convallis ipsum ac odio aliquet tincidunt. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Quisque convallis ipsum ac odio aliquet tincidunt. Mauris ut magna vitae mauris fringilla condimentum. -Proin non mi placerat, ultricies diam sit amet, ultricies nisi. + Proin non mi placerat, ultricies diam sit amet, ultricies nisi. + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Quisque convallis ipsum ac odio aliquet tincidunt. + + Mauris ut magna vitae mauris fringilla condimentum. + Proin non mi placerat, ultricies diam sit amet, ultricies nisi. diff --git a/test_data/tst_textdocument/indent/indent.txt.original b/test_data/tst_textdocument/indent/indent.txt.original index 85d2f04b..78506b82 100644 --- a/test_data/tst_textdocument/indent/indent.txt.original +++ b/test_data/tst_textdocument/indent/indent.txt.original @@ -1,10 +1,10 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque convallis ipsum ac odio aliquet tincidunt. Mauris ut magna vitae mauris fringilla condimentum. Proin non mi placerat, ultricies diam sit amet, ultricies nisi. - Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque convallis ipsum ac odio aliquet tincidunt. Mauris ut magna vitae mauris fringilla condimentum. @@ -16,8 +16,14 @@ Quisque convallis ipsum ac odio aliquet tincidunt. Mauris ut magna vitae mauris fringilla condimentum. Proin non mi placerat, ultricies diam sit amet, ultricies nisi. -Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque convallis ipsum ac odio aliquet tincidunt. Mauris ut magna vitae mauris fringilla condimentum. -Proin non mi placerat, ultricies diam sit amet, ultricies nisi. + Proin non mi placerat, ultricies diam sit amet, ultricies nisi. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Quisque convallis ipsum ac odio aliquet tincidunt. + +Mauris ut magna vitae mauris fringilla condimentum. + Proin non mi placerat, ultricies diam sit amet, ultricies nisi. diff --git a/tests/tst_textdocument.cpp b/tests/tst_textdocument.cpp index 802ea2d2..de566c1a 100644 --- a/tests/tst_textdocument.cpp +++ b/tests/tst_textdocument.cpp @@ -349,26 +349,66 @@ private slots: void indent() { + auto spaces = [](int count) { + return QString(count, ' '); + }; +#define COMPARE_LINE_INDENT(LINE, TEXT, INDENT) \ + QCOMPARE(document.indentTextAtLine(LINE), TEXT); \ + QCOMPARE(document.indentationAtLine(LINE), INDENT); + +#define COMPARE_CURRENT_INDENT(TEXT, INDENT) \ + QCOMPARE(document.indentationAtPosition(document.position()), INDENT); \ + QCOMPARE(document.indentationAtLine(-1), INDENT); \ + QCOMPARE(document.indentTextAtPosition(document.position()), TEXT); \ + QCOMPARE(document.indentTextAtLine(-1), TEXT); + Test::FileTester file(Test::testDataPath() + "/tst_textdocument/indent/indent.txt"); { Core::KnutCore core; Core::TextDocument document; document.load(file.fileName()); + COMPARE_LINE_INDENT(1, spaces(2), 0); + COMPARE_LINE_INDENT(7, " \t\t\t\t", 4); + COMPARE_LINE_INDENT(29, spaces(4), 1); + // If the line number is larger than the line count, an empty string is returned + COMPARE_LINE_INDENT(50, "", 0); + document.gotoLine(4); document.indent(); + COMPARE_CURRENT_INDENT(spaces(4), 1) + + // Test that we can correctly detect columns in mixed tabs and spaces. + // In this test, the first column contains 2 spaces and a tab. + // Because our tabsize is 4 spaces, the first 2 spaces and the tab combine into the first column. + // When we remove 2 levels of indentation, that will result in 2 columns left (aka. 8 spaces). document.gotoLine(7, 4); - document.removeIndent(2); + COMPARE_CURRENT_INDENT(" \t\t\t\t", 4) + document.indent(-2); + COMPARE_CURRENT_INDENT(spaces(8), 2) + document.gotoLine(10); document.selectNextLine(); document.indent(); + document.gotoLine(16); document.selectNextLine(); - document.removeIndent(); + document.indent(-1); + + document.gotoLine(19); + document.selectNextLine(); + document.setIndentation(1); + + document.indentLine(2, 25); + document.indentLine(-1, 26); + document.setIndentationAtLine(1, 28); + document.save(); QVERIFY(file.compare()); } +#undef COMPARE_CURRENT_INDENT +#undef COMPARE_LINE_INDENT } void findReplace()