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