From 250783f63b397e1aad89044385d0eaa3e55d3077 Mon Sep 17 00:00:00 2001 From: Dany Thinnes Date: Sun, 24 May 2026 13:09:00 +0200 Subject: [PATCH] =?UTF-8?q?-=20Ausr=C3=BCcken=20mit=20Shift-Tab=20implemen?= =?UTF-8?q?tiert=20-=20R=C3=9Ccksprung=20mit=20Backspace=20implementiert?= =?UTF-8?q?=20-=20Anzeige=20f=C3=BCr=20ver=C3=A4nderte=20Datei=20implement?= =?UTF-8?q?iert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/CodeEditor.cpp | 138 ++++++++++++++++++++++++++++++++----- src/editor/EditorPanel.cpp | 16 ++++- 2 files changed, 137 insertions(+), 17 deletions(-) diff --git a/src/editor/CodeEditor.cpp b/src/editor/CodeEditor.cpp index 8ad7049..367d1fd 100644 --- a/src/editor/CodeEditor.cpp +++ b/src/editor/CodeEditor.cpp @@ -269,19 +269,77 @@ void CodeEditor::highlightCurrentLine() } // --------------------------------------------------------------------------- -// Key handling – auto-indent + Tab → spaces +// Key handling – Tab/Shift+Tab, Smart-Backspace, Auto-Indent // --------------------------------------------------------------------------- void CodeEditor::keyPressEvent(QKeyEvent *event) { - // Tab key: insert spaces instead of a real tab character - if (event->key() == Qt::Key_Tab && m_settings->useSpacesForTabs()) + const int tabSize = m_settings->tabSize(); + + // ----------------------------------------------------------------------- + // Shift+Tab: Einrückung zurückziehen + // ----------------------------------------------------------------------- + if (event->key() == Qt::Key_Backtab || + (event->key() == Qt::Key_Tab && event->modifiers() & Qt::ShiftModifier)) + { + QTextCursor cursor = textCursor(); + + QTextBlock startBlock = document()->findBlock(cursor.selectionStart()); + QTextBlock endBlock = document()->findBlock(cursor.selectionEnd()); + + cursor.beginEditBlock(); + for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next()) + { + const QString lineText = b.text(); + int toRemove = 0; + + if (m_settings->useSpacesForTabs()) + { + // Bis zu tabSize führende Leerzeichen entfernen + for (int i = 0; i < tabSize && i < lineText.length(); ++i) + { + if (lineText[i] == ' ') + { + ++toRemove; + } + else + { + break; + } + } + } + else + { + // Einen führenden Tab entfernen + if (!lineText.isEmpty() && lineText[0] == '\t') + { + toRemove = 1; + } + } + + if (toRemove > 0) + { + QTextCursor lineCursor(b); + lineCursor.movePosition(QTextCursor::StartOfBlock); + lineCursor.movePosition(QTextCursor::Right, + QTextCursor::KeepAnchor, + toRemove); + lineCursor.removeSelectedText(); + } + } + cursor.endEditBlock(); + return; + } + + // ----------------------------------------------------------------------- + // Tab: Einrücken (Leerzeichen oder echter Tab) + // ----------------------------------------------------------------------- + if (event->key() == Qt::Key_Tab) { - const int tabSize = m_settings->tabSize(); QTextCursor cursor = textCursor(); if (cursor.hasSelection()) { - // Indent selected lines + // Mehrere Zeilen einrücken QTextBlock startBlock = document()->findBlock(cursor.selectionStart()); QTextBlock endBlock = document()->findBlock(cursor.selectionEnd()); @@ -289,27 +347,76 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next()) { QTextCursor lineCursor(b); - lineCursor.insertText(QString(tabSize, ' ')); + lineCursor.movePosition(QTextCursor::StartOfBlock); + if (m_settings->useSpacesForTabs()) + { + lineCursor.insertText(QString(tabSize, ' ')); + } + else + { + lineCursor.insertText("\t"); + } } cursor.endEditBlock(); } else { - // Calculate spaces needed to reach next tab stop - const int col = cursor.columnNumber(); - const int spacesNeeded = tabSize - (col % tabSize); - cursor.insertText(QString(spacesNeeded, ' ')); + if (m_settings->useSpacesForTabs()) + { + // Zum nächsten Tab-Stop auffüllen + const int col = cursor.columnNumber(); + const int spacesNeeded = tabSize - (col % tabSize); + cursor.insertText(QString(spacesNeeded, ' ')); + } + else + { + cursor.insertText("\t"); + } } return; } - // Enter / Return: auto-indent + // ----------------------------------------------------------------------- + // Smart Backspace: springt zur vorherigen Einrückungsstufe + // ----------------------------------------------------------------------- + if (event->key() == Qt::Key_Backspace + && !textCursor().hasSelection() + && m_settings->useSpacesForTabs()) + { + QTextCursor cursor = textCursor(); + const int col = cursor.columnNumber(); + + if (col > 0) + { + // Prüfen ob links vom Cursor nur Leerzeichen bis Zeilenbeginn stehen + const QString lineText = cursor.block().text(); + const QString leftOfCursor = lineText.left(col); + const bool onlySpaces = leftOfCursor.trimmed().isEmpty(); + + if (onlySpaces && col > 0) + { + // Zur vorherigen Tab-Stop-Position springen + const int targetCol = ((col - 1) / tabSize) * tabSize; + const int toDelete = col - targetCol; + + cursor.movePosition(QTextCursor::Left, + QTextCursor::KeepAnchor, + toDelete); + cursor.removeSelectedText(); + return; + } + } + } + + // ----------------------------------------------------------------------- + // Enter / Return: Auto-Indent + // ----------------------------------------------------------------------- if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { QTextCursor cursor = textCursor(); const QString currentLine = cursor.block().text(); - // Count leading whitespace + // Führende Leerzeichen der aktuellen Zeile zählen int leadingSpaces = 0; for (const QChar &ch : currentLine) { @@ -319,7 +426,7 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) } else if (ch == '\t') { - leadingSpaces += m_settings->tabSize(); + leadingSpaces += tabSize; } else { @@ -327,15 +434,13 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) } } - // Let the base class insert the newline first QPlainTextEdit::keyPressEvent(event); - // Then re-indent if (leadingSpaces > 0) { const QString indent = m_settings->useSpacesForTabs() ? QString(leadingSpaces, ' ') - : QString(leadingSpaces / m_settings->tabSize(), '\t'); + : QString(leadingSpaces / tabSize, '\t'); textCursor().insertText(indent); } return; @@ -343,3 +448,4 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) QPlainTextEdit::keyPressEvent(event); } + diff --git a/src/editor/EditorPanel.cpp b/src/editor/EditorPanel.cpp index bffe04e..ec06d60 100644 --- a/src/editor/EditorPanel.cpp +++ b/src/editor/EditorPanel.cpp @@ -4,6 +4,7 @@ #include "SearchPanel.h" #include +#include EditorPanel::EditorPanel(Settings *settings, QWidget *parent) : QWidget(parent) @@ -70,7 +71,20 @@ void EditorPanel::openFile(const QString &filePath) m_tabWidget->setTabToolTip(index, filePath); m_openTabs.insert(filePath, tab); - // Tab-Titel nach "Speichern unter" aktualisieren + // Änderungsindikator im Tab-Titel (● = ungespeichert) + connect(tab->editor()->document(), &QTextDocument::modificationChanged, + this, [this, tab](bool modified) + { + const int idx = m_tabWidget->indexOf(tab); + if (idx == -1) + { + return; + } + const QString name = QFileInfo(tab->filePath()).fileName(); + m_tabWidget->setTabText(idx, modified ? "● " + name : name); + }); + + // Tab-Titel nach "Speichern unter" aktualisieren (neuer Dateiname, kein Punkt) connect(tab->editor(), &CodeEditor::fileSaved, this, [this, tab](const QString &savedPath) { const int idx = m_tabWidget->indexOf(tab);