Indentation and (un)commenting in local layout and preamble
Daniel
xracoonx at gmx.de
Sun Aug 14 18:20:20 UTC 2022
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
-------------- next part --------------
From aef456245b29a2563204b021d58c944641be4a0e Mon Sep 17 00:00:00 2001
From: Daniel Ramoeller <d.lyx at 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)
More information about the lyx-devel
mailing list