On 2022-08-14 18:38, Kornel Benko wrote:
Applies cleanly and compiles. Rudimentary test looks good. (At least writing 
local
format feels better than what we have now).

        Kornel

Thanks for testing. I realized that I changed the tab stop size before setting the font which could lead to a wrong size. Very slightly modified patch attached. I only added

preambleTE->setTabStop(4);

and

locallayoutTE->setTabStop(4);

at the correct positions.

Daniel
From aef456245b29a2563204b021d58c944641be4a0e Mon Sep 17 00:00:00 2001
From: Daniel Ramoeller <d....@web.de>
Date: Sun, 14 Aug 2022 20:16:02 +0200
Subject: [PATCH] Extended comment and indentation for source code

- automatically inherit indentation from previous block
- (un)indent blocks
- (un)comment blocks
---
 src/frontends/qt/GuiDocument.cpp     |  23 +--
 src/frontends/qt/GuiSourceEdit.cpp   | 258 +++++++++++++++++++++++++++
 src/frontends/qt/GuiSourceEdit.h     |  57 ++++++
 src/frontends/qt/Makefile.am         |   2 +
 src/frontends/qt/ui/LocalLayoutUi.ui |   9 +-
 src/frontends/qt/ui/PreambleUi.ui    |   9 +-
 6 files changed, 336 insertions(+), 22 deletions(-)
 create mode 100644 src/frontends/qt/GuiSourceEdit.cpp
 create mode 100644 src/frontends/qt/GuiSourceEdit.h

diff --git a/src/frontends/qt/GuiDocument.cpp b/src/frontends/qt/GuiDocument.cpp
index b3f31302dd..7543d7126e 100644
--- a/src/frontends/qt/GuiDocument.cpp
+++ b/src/frontends/qt/GuiDocument.cpp
@@ -480,7 +480,8 @@ PreambleModule::PreambleModule(QWidget * parent)
        // @ is letter in the LyX user preamble
        (void) new LaTeXHighlighter(preambleTE->document(), true);
        preambleTE->setFont(guiApp->typewriterSystemFont());
-       preambleTE->setWordWrapMode(QTextOption::NoWrap);
+       preambleTE->setTabStop(4);
+       preambleTE->setSingleLine("%");
        setFocusProxy(preambleTE);
        connect(preambleTE, SIGNAL(textChanged()), this, SIGNAL(changed()));
        connect(findLE, SIGNAL(textEdited(const QString &)), this, 
SLOT(checkFindButton()));
@@ -488,15 +489,6 @@ PreambleModule::PreambleModule(QWidget * parent)
        connect(editPB, SIGNAL(clicked()), this, SLOT(editExternal()));
        connect(findLE, SIGNAL(returnPressed()), this, SLOT(findText()));
        checkFindButton();
-       int const tabStop = 4;
-       QFontMetrics metrics(preambleTE->currentFont());
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
-       // horizontalAdvance() is available starting in 5.11.0
-       // setTabStopDistance() is available starting in 5.10.0
-       preambleTE->setTabStopDistance(tabStop * metrics.horizontalAdvance(' 
'));
-#else
-       preambleTE->setTabStopWidth(tabStop * metrics.width(' '));
-#endif
 }
 
 
@@ -606,20 +598,11 @@ LocalLayout::LocalLayout(QWidget * parent)
        : UiWidget<Ui::LocalLayoutUi>(parent), current_id_(nullptr), 
validated_(false)
 {
        locallayoutTE->setFont(guiApp->typewriterSystemFont());
-       locallayoutTE->setWordWrapMode(QTextOption::NoWrap);
+       locallayoutTE->setTabStop(4);
        connect(locallayoutTE, SIGNAL(textChanged()), this, 
SLOT(textChanged()));
        connect(validatePB, SIGNAL(clicked()), this, SLOT(validatePressed()));
        connect(convertPB, SIGNAL(clicked()), this, SLOT(convertPressed()));
        connect(editPB, SIGNAL(clicked()), this, SLOT(editExternal()));
-       int const tabStop = 4;
-       QFontMetrics metrics(locallayoutTE->currentFont());
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
-       // horizontalAdvance() is available starting in 5.11.0
-       // setTabStopDistance() is available starting in 5.10.0
-       locallayoutTE->setTabStopDistance(tabStop * metrics.horizontalAdvance(' 
'));
-#else
-       locallayoutTE->setTabStopWidth(tabStop * metrics.width(' '));
-#endif
 }
 
 
diff --git a/src/frontends/qt/GuiSourceEdit.cpp 
b/src/frontends/qt/GuiSourceEdit.cpp
new file mode 100644
index 0000000000..d011a8aad6
--- /dev/null
+++ b/src/frontends/qt/GuiSourceEdit.cpp
@@ -0,0 +1,258 @@
+// -*- C++ -*-
+/**
+ * \file GuiSourceEdit.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include "GuiSourceEdit.h"
+
+namespace lyx {
+namespace frontend {
+
+GuiSourceEdit::GuiSourceEdit(QWidget * parent) : QTextEdit(parent)
+{
+       setWordWrapMode(QTextOption::NoWrap);
+       setTabStop(tabStop_);
+}
+
+void GuiSourceEdit::setTabStop(int spaces) {
+       tabStop_ = spaces;
+       QFontMetrics metrics(currentFont());
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
+       // horizontalAdvance() is available starting in 5.11.0
+       // setTabStopDistance() is available starting in 5.10.0
+       setTabStopDistance(tabStop_ * metrics.horizontalAdvance(' '));
+#else
+       setTabStopWidth(tabStop_ * metrics.width(' '));
+#endif
+}
+
+GuiSourceEdit::BlockRangeInfo GuiSourceEdit::getBlockRangeInfo(
+               QTextCursor const & cursorIn) const {
+       QTextCursor cursor = cursorIn;
+       QTextDocument * doc = cursor.document();
+
+       int pos = cursor.position();
+       int anchor = cursor.anchor();
+       int start = qMin(anchor, pos);
+       int end = qMax(anchor, pos);
+       bool anchorIsStart = (anchor == start);
+
+       QTextBlock startBlock = doc->findBlock(start);
+       QTextBlock endBlock = doc->findBlock(end);
+
+       return { pos, anchor, start, end, anchorIsStart, startBlock, endBlock };
+}
+
+void GuiSourceEdit::keyPressEvent(QKeyEvent * event)
+{
+
+       QTextCursor cursor = textCursor();
+       BlockRangeInfo blockRangeInfo = getBlockRangeInfo(cursor);
+       // (un)commenting via shift + slash
+       if ((event->modifiers() & Qt::ControlModifier) && event->key() == 
Qt::Key_Slash)
+               unCommentSelection(cursor);
+       // multi-line indentation via tab key
+       else if (event->key() == Qt::Key_Tab &&
+                        blockRangeInfo.startBlock != blockRangeInfo.endBlock)
+               unIndent(cursor);
+       // unindent via backtab (typically shift+tab)
+       else if (event->key() == Qt::Key_Backtab)
+               unIndent(cursor, true);
+       // inherit indentation from previous line via return key
+       else if (event->key() == Qt::Key_Return) {
+               QTextEdit::keyPressEvent(event);
+               inheritIndent(cursor);
+       } else
+               return QTextEdit::keyPressEvent(event);
+}
+
+QTextCursor GuiSourceEdit::inheritIndent(QTextCursor const & cursorIn) {
+       QTextCursor cursor = cursorIn;
+       BlockRangeInfo blockRangeInfo = getBlockRangeInfo(cursor);
+       cursor.beginEditBlock();
+
+       QTextBlock previousBlock = blockRangeInfo.startBlock.previous();
+
+       const QString text = previousBlock.text();
+       for (QChar c : text) {
+               if (c == QChar::Tabulation)
+                       cursor.insertText("\t", QTextCharFormat());
+               else
+                       break;
+       }
+
+       cursor.endEditBlock();
+       return cursor;
+}
+
+QTextCursor GuiSourceEdit::unIndent(QTextCursor const & cursorIn, bool 
unIndent) {
+       QTextCursor cursor = cursorIn;
+       BlockRangeInfo blockRangeInfo = getBlockRangeInfo(cursor);
+
+       cursor.beginEditBlock();
+       blockRangeInfo.endBlock = blockRangeInfo.endBlock.next();
+
+       if (blockRangeInfo.end > blockRangeInfo.start &&
+                       blockRangeInfo.endBlock.position() == 
blockRangeInfo.end) {
+               --blockRangeInfo.end;
+               blockRangeInfo.endBlock = blockRangeInfo.endBlock.previous();
+       }
+
+       bool hasSelection = cursor.hasSelection();
+       if (unIndent) {
+               for (QTextBlock block = blockRangeInfo.startBlock;
+                        block != blockRangeInfo.endBlock; block = 
block.next()) {
+                       if (block.text().at(0) == '\t') {
+                               cursor.setPosition(block.position());
+                               cursor.movePosition(QTextCursor::NextCharacter,
+                                                   QTextCursor::KeepAnchor, 1);
+                               cursor.removeSelectedText();
+                       }
+               }
+       } else {
+               for (QTextBlock block = blockRangeInfo.startBlock;
+                        block != blockRangeInfo.endBlock; block = 
block.next()) {
+                       cursor.setPosition(block.position());
+                       cursor.insertText("\t", QTextCharFormat());
+               }
+       }
+       cursor.endEditBlock();
+
+       cursor = cursorIn;
+       if (hasSelection && !unIndent) {
+               blockRangeInfo.start = blockRangeInfo.startBlock.position();
+               int lastSelPos = blockRangeInfo.anchorIsStart ? 
cursor.position()
+                                                             : cursor.anchor();
+               if (blockRangeInfo.anchorIsStart) {
+                       cursor.setPosition(blockRangeInfo.start);
+                       cursor.setPosition(lastSelPos, QTextCursor::KeepAnchor);
+               } else {
+                       cursor.setPosition(lastSelPos);
+                       cursor.setPosition(blockRangeInfo.start, 
QTextCursor::KeepAnchor);
+               }
+       }
+       return cursor;
+}
+
+// Slightly modified version of Qt Creator's unCommentSelection
+QTextCursor GuiSourceEdit::unCommentSelection(QTextCursor const & cursorIn)
+{
+       QTextCursor cursor = cursorIn;
+       BlockRangeInfo blockRangeInfo = getBlockRangeInfo(cursor);
+       cursor.beginEditBlock();
+
+       if (blockRangeInfo.end > blockRangeInfo.start &&
+                       blockRangeInfo.endBlock.position() == 
blockRangeInfo.end) {
+               --blockRangeInfo.end;
+               blockRangeInfo.endBlock = blockRangeInfo.endBlock.previous();
+       }
+
+       bool hasSelection = cursor.hasSelection();
+
+       bool oneBlock = blockRangeInfo.startBlock == blockRangeInfo.endBlock;
+
+       blockRangeInfo.endBlock = blockRangeInfo.endBlock.next();
+       bool doUncomment = true;
+
+       // Check whether uncommenting
+       for (QTextBlock block = blockRangeInfo.startBlock;
+                block != blockRangeInfo.endBlock; block = block.next()) {
+               QString text = block.text().trimmed();
+               if ((oneBlock || !text.isEmpty()) && 
!text.startsWith(singleLine_)) {
+                       doUncomment = false;
+                       break;
+               }
+       }
+
+       // Determine for minimal indentation (tabs)
+       int minIndent = 1000;
+       for (QTextBlock block = blockRangeInfo.startBlock;
+                block != blockRangeInfo.endBlock; block = block.next()) {
+               const QString text = block.text();
+               int tabs = 0;
+               for (QChar c : text) {
+                       if (c == QChar::Tabulation)
+                               tabs++;
+                       if (!c.isSpace())
+                               break;
+               }
+               minIndent = qMin(minIndent, tabs);
+       }
+
+       if (minIndent == 1000) minIndent = 0;
+
+       const int singleLine_Length = singleLine_.length();
+       for (QTextBlock block = blockRangeInfo.startBlock;
+                block != blockRangeInfo.endBlock; block = block.next()) {
+               if (doUncomment) {
+                       QString text = block.text();
+                       int i = 0;
+                       while (i <= text.size() - singleLine_Length) {
+                               if (isComment(text, i, singleLine_)) {
+                                       // Check for whether there is a space 
after the comment
+                                       bool hasSpace = false;
+                                       if (text.size() > i + 1 && text.at(i + 
1) == ' ')
+                                               hasSpace = true;
+                                       cursor.setPosition(block.position() + 
i);
+                                       
cursor.movePosition(QTextCursor::NextCharacter,
+                                                           
QTextCursor::KeepAnchor,
+                                                           singleLine_Length + 
(hasSpace ? 1 : 0));
+                                       cursor.removeSelectedText();
+                                       break;
+                               }
+                               if (!text.at(i).isSpace())
+                                       break;
+                               ++i;
+                       }
+               } else {
+                       if (!block.text().trimmed().isEmpty() || oneBlock) {
+                               cursor.setPosition(block.position() + 
minIndent);
+                               // Insert comment string with space and without 
formatting
+                               cursor.insertText(singleLine_ + " ", 
QTextCharFormat());
+                       }
+               }
+       }
+
+       cursor.endEditBlock();
+
+       cursor = cursorIn;
+       // adjust selection when commenting out
+       if (hasSelection && !doUncomment) {
+               blockRangeInfo.start = blockRangeInfo.startBlock.position(); // 
move the comment into the selection
+               int lastSelPos = blockRangeInfo.anchorIsStart ? 
cursor.position()
+                                                             : cursor.anchor();
+               if (blockRangeInfo.anchorIsStart) {
+                       cursor.setPosition(blockRangeInfo.start);
+                       cursor.setPosition(lastSelPos, QTextCursor::KeepAnchor);
+               } else {
+                       cursor.setPosition(lastSelPos);
+                       cursor.setPosition(blockRangeInfo.start, 
QTextCursor::KeepAnchor);
+               }
+       }
+       return cursor;
+}
+
+bool GuiSourceEdit::isComment(const QString & text, int index,
+   const QString & commentType)
+{
+       const int length = commentType.length();
+
+       Q_ASSERT(text.length() - index >= length);
+
+       int i = 0;
+       while (i < length) {
+               if (text.at(index + i) != commentType.at(i))
+                       return false;
+               ++i;
+       }
+       return true;
+}
+
+}
+}
+
+#include "moc_GuiSourceEdit.cpp"
diff --git a/src/frontends/qt/GuiSourceEdit.h b/src/frontends/qt/GuiSourceEdit.h
new file mode 100644
index 0000000000..c88c80c626
--- /dev/null
+++ b/src/frontends/qt/GuiSourceEdit.h
@@ -0,0 +1,57 @@
+// -*- C++ -*-
+/**
+ * \file GuiClickableLabel.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef GUISOURCEEDIT_H
+#define GUISOURCEEDIT_H
+
+#include <QTextBlock>
+#include <QTextEdit>
+
+namespace lyx {
+namespace frontend {
+
+class GuiSourceEdit : public QTextEdit
+{
+       Q_OBJECT
+public:
+       GuiSourceEdit(QWidget * parent);
+       void setTabStop(int spaces);
+       int tabStop() { return tabStop_; };
+       void setSingleLine(QString const & singleLine) { singleLine_ = 
singleLine; };
+       QString singleLine() { return singleLine_; };
+
+private:
+       struct BlockRangeInfo {
+               int pos;
+               int anchor;
+               int start;
+               int end;
+               bool anchorIsStart;
+               QTextBlock startBlock;
+               QTextBlock endBlock;
+       };
+
+       BlockRangeInfo getBlockRangeInfo(QTextCursor const & cursorIn) const;
+       void keyPressEvent(QKeyEvent * event);
+       // inherit indentation from previous block
+       QTextCursor inheritIndent(QTextCursor const & cursorIn);
+       // (un)indent blocks
+       QTextCursor unIndent(QTextCursor const & cursorIn, bool unIndent = 
false);
+       // (un)comment blocks
+       QTextCursor unCommentSelection(QTextCursor const & cursorIn);
+       bool isComment(const QString & text, int index, const QString & 
commentType);
+
+       int tabStop_ = 4;
+       QString singleLine_ = "#";
+};
+
+}
+}
+
+#endif // GUISOURCEEDIT_H
diff --git a/src/frontends/qt/Makefile.am b/src/frontends/qt/Makefile.am
index 9ca258d9d3..486b28fd9b 100644
--- a/src/frontends/qt/Makefile.am
+++ b/src/frontends/qt/Makefile.am
@@ -118,6 +118,7 @@ SOURCEFILES = \
        GuiSendto.cpp \
        GuiSetBorder.cpp \
        GuiShowFile.cpp \
+       GuiSourceEdit.cpp \
        GuiSpellchecker.cpp \
        GuiSymbols.cpp \
        GuiTabular.cpp \
@@ -232,6 +233,7 @@ MOCHEADER = \
        GuiSendto.h \
        GuiSetBorder.h \
        GuiShowFile.h \
+       GuiSourceEdit.h \
        GuiSpellchecker.h \
        GuiSymbols.h \
        GuiTabularCreate.h \
diff --git a/src/frontends/qt/ui/LocalLayoutUi.ui 
b/src/frontends/qt/ui/LocalLayoutUi.ui
index fc753b7c15..bcc71e14e5 100644
--- a/src/frontends/qt/ui/LocalLayoutUi.ui
+++ b/src/frontends/qt/ui/LocalLayoutUi.ui
@@ -15,7 +15,7 @@
   </property>
   <layout class="QGridLayout" name="gridLayout_2">
    <item row="0" column="0">
-    <widget class="QTextEdit" name="locallayoutTE">
+    <widget class="lyx::frontend::GuiSourceEdit" name="locallayoutTE">
      <property name="toolTip">
       <string>Document-specific layout information</string>
      </property>
@@ -105,6 +105,13 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>lyx::frontend::GuiSourceEdit</class>
+   <extends>QTextEdit</extends>
+   <header>GuiSourceEdit.h</header>
+  </customwidget>
+ </customwidgets>
  <includes>
   <include location="local">qt_i18n.h</include>
  </includes>
diff --git a/src/frontends/qt/ui/PreambleUi.ui 
b/src/frontends/qt/ui/PreambleUi.ui
index 8a7015c6dd..43084ee315 100644
--- a/src/frontends/qt/ui/PreambleUi.ui
+++ b/src/frontends/qt/ui/PreambleUi.ui
@@ -50,7 +50,7 @@
     </widget>
    </item>
    <item row="0" column="0" colspan="3">
-    <widget class="QTextEdit" name="preambleTE">
+    <widget class="lyx::frontend::GuiSourceEdit" name="preambleTE">
      <property name="acceptRichText">
       <bool>false</bool>
      </property>
@@ -58,6 +58,13 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>lyx::frontend::GuiSourceEdit</class>
+   <extends>QTextEdit</extends>
+   <header>GuiSourceEdit.h</header>
+  </customwidget>
+ </customwidgets>
  <includes>
   <include location="local">qt_i18n.h</include>
  </includes>
-- 
2.24.3 (Apple Git-128)

-- 
lyx-devel mailing list
lyx-devel@lists.lyx.org
http://lists.lyx.org/mailman/listinfo/lyx-devel

Reply via email to