diff --git a/share/openfluid/waresdev/cmake.syntaxhl.ofdev.xml b/share/openfluid/waresdev/cmake.syntaxhl.ofdev.xml
index 124816aaf..cb7e41796 100644
--- a/share/openfluid/waresdev/cmake.syntaxhl.ofdev.xml
+++ b/share/openfluid/waresdev/cmake.syntaxhl.ofdev.xml
@@ -483,7 +483,7 @@
-
+
diff --git a/src/openfluid/ui/waresdev/WareSrcFileEditor.cpp b/src/openfluid/ui/waresdev/WareSrcFileEditor.cpp
index 77f948731..8427b699a 100644
--- a/src/openfluid/ui/waresdev/WareSrcFileEditor.cpp
+++ b/src/openfluid/ui/waresdev/WareSrcFileEditor.cpp
@@ -330,9 +330,38 @@ void WareSrcFileEditor::keyPressEvent(QKeyEvent* Event)
{
insertNewLine();
}
+ else if ((Event->modifiers() & Qt::ShiftModifier) && Key == Qt::Key_Backtab)
+ {
+ // Unindentation
+ QTextCursor TextCursor = textCursor();
+ if(TextCursor.hasSelection())
+ {
+ removeMultilinePrefix(m_IndentString);
+ }
+ else
+ {
+ removeLinePrefix(m_IndentString, TextCursor.block());
+ }
+ }
else if (Key == Qt::Key_Tab)
{
- insertPlainText(m_IndentString);
+ // Indentation
+ QTextCursor TextCursor = textCursor();
+ int StartLine = document()->findBlock(TextCursor.selectionStart()).firstLineNumber();
+ int EndLine = document()->findBlock(TextCursor.selectionEnd()).firstLineNumber();
+ if(TextCursor.hasSelection() && (StartLine != EndLine))
+ {
+ addMultilinePrefix(m_IndentString);
+ }
+ else
+ {
+ insertPlainText(m_IndentString);
+ }
+ }
+ else if ((Event->modifiers() & Qt::ControlModifier) && Key == Qt::Key_Slash)
+ {
+ // Comment / Uncomment
+ handleSelectionCommenting();
}
else if ((Event->modifiers() & Qt::ControlModifier) && Key == Qt::Key_I)
{
@@ -406,6 +435,288 @@ void WareSrcFileEditor::keyPressEvent(QKeyEvent* Event)
// =====================================================================
+void WareSrcFileEditor::handleSelectionCommenting()
+{
+ QString CommentStr = openfluid::ui::waresdev::WareSrcFiletypeManager::instance()
+ ->getFileCommentString(m_FilePath);
+ if(!CommentStr.isEmpty())
+ {
+ QTextCursor TextCursor = textCursor();
+ QTextBlock Block = TextCursor.block();
+ int EndPos = TextCursor.selectionEnd();
+ bool NeedComments = false;
+ QString PrettyCommentStr = CommentStr + CommentExtraSpaceString;
+ if(TextCursor.hasSelection())
+ {
+ while (Block.isValid() && (Block.position() < EndPos))
+ {
+ if(!Block.text().isEmpty() && !Block.text().trimmed().startsWith(CommentStr))
+ {
+ NeedComments = true;
+ break;
+ }
+ Block = Block.next();
+ }
+ if(NeedComments)
+ {
+ addMultilinePrefix(PrettyCommentStr);
+ }
+ else
+ {
+ removeMultilinePrefix(CommentStr);
+ }
+ }
+ else
+ {
+ if(!Block.text().isEmpty() && Block.text().trimmed().startsWith(CommentStr))
+ {
+ removeLinePrefix(CommentStr, Block);
+ }
+ else
+ {
+ addLinePrefix(PrettyCommentStr, Block);
+ }
+ }
+ }
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+bool WareSrcFileEditor::hasCharInBlockBeforePos(int Pos)
+{
+ QTextCursor Cursor = textCursor();
+ Cursor.setPosition(Pos);
+ QTextBlock Block = Cursor.block();
+ if (Pos > Block.position())
+ {
+ Cursor.setPosition(Pos, QTextCursor::MoveAnchor);
+ Cursor.setPosition(Block.position(), QTextCursor::KeepAnchor);
+ return getLeadingSpacesCount(Cursor.selectedText()) != Cursor.selectedText().length();
+ }
+
+ return false;
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+int WareSrcFileEditor::getLeadingSpacesCount(const QString& QStr)
+{
+ int Count = 0;
+ for (QChar Char : QStr)
+ {
+ if (!Char.isSpace())
+ {
+ return Count;
+ }
+ Count++;
+ }
+
+ return Count;
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+WareSrcFileEditor::SelectionData WareSrcFileEditor::createSelectionData()
+{
+ SelectionData Data;
+ Data.TextCursor = textCursor();
+ Data.CursorPos = Data.TextCursor.position();
+ Data.StartPos = Data.TextCursor.selectionStart();
+ Data.EndPos = Data.TextCursor.selectionEnd();
+ Data.FirstBlock = document()->findBlock(Data.StartPos);
+
+ return Data;
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+void WareSrcFileEditor::selectText(int StartPos, int EndPos)
+{
+ QTextCursor TextCursor = textCursor();
+ TextCursor.setPosition(StartPos);
+ TextCursor.setPosition(EndPos, QTextCursor::KeepAnchor);
+ setTextCursor(TextCursor);
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+void WareSrcFileEditor::addLinePrefix(const QString& PrefixStr, const QTextBlock& Block)
+{
+ QTextCursor TextCursor = textCursor();
+ int CursorPos = TextCursor.position();
+ /* Indicates the start of a block of editing operations on the document
+ that should appear as a single operation from an undo/redo point of view. */
+ TextCursor.beginEditBlock();
+
+ int LeadingSpacesCount = getLeadingSpacesCount(Block.text());
+ QString UpdatedText = Block.text().insert(LeadingSpacesCount, PrefixStr);
+ selectText(Block.position(), Block.position() + Block.length()-1);
+ insertPlainText(UpdatedText);
+ TextCursor.setPosition(CursorPos + (hasCharInBlockBeforePos(CursorPos) ? PrefixStr.length() : 0));
+ setTextCursor(TextCursor);
+
+ TextCursor.endEditBlock();
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+int WareSrcFileEditor::removeLinePrefix(const QString& PrefixStr, const QTextBlock& Block)
+{
+ QTextCursor TextCursor = textCursor();
+ int LeadingSpacesCount = getLeadingSpacesCount(Block.text());
+ int RemoveSize = 0;
+ bool ShouldRemove = false;
+ QString UpdatedText;
+ int CursorPos = TextCursor.position();
+ TextCursor.beginEditBlock();
+
+ // Specific use case for unindentation
+ if(PrefixStr == m_IndentString && LeadingSpacesCount > 0)
+ {
+ RemoveSize = (LeadingSpacesCount > m_IndentString.length()) ? m_IndentString.length() : LeadingSpacesCount;
+ UpdatedText = Block.text().remove(0, RemoveSize);
+ ShouldRemove = true;
+ }
+ else if(Block.text().trimmed().startsWith(PrefixStr))
+ {
+ int ExtraSpacePos = Block.position() + LeadingSpacesCount + PrefixStr.length();
+ TextCursor.setPosition(ExtraSpacePos);
+ TextCursor.setPosition(ExtraSpacePos + CommentExtraSpaceString.length(), QTextCursor::KeepAnchor);
+ setTextCursor(TextCursor);
+ // Remove extra first space after prefix
+ int ExtraSize = (!TextCursor.selectedText().isEmpty() && TextCursor.selectedText().trimmed().isEmpty()) ?
+ CommentExtraSpaceString.length() : 0;
+ UpdatedText = Block.text().remove(LeadingSpacesCount, PrefixStr.length() + ExtraSize);
+ RemoveSize = PrefixStr.length() + ExtraSize;
+ ShouldRemove = true;
+ }
+
+ if(ShouldRemove)
+ {
+ bool ShouldStepBackCursor = hasCharInBlockBeforePos(CursorPos) || PrefixStr == m_IndentString;
+ selectText(Block.position(), Block.position() + Block.length()-1);
+ insertPlainText(UpdatedText);
+ TextCursor.setPosition(std::max(CursorPos - (ShouldStepBackCursor ? RemoveSize : 0), Block.position()));
+ setTextCursor(TextCursor);
+ }
+ TextCursor.endEditBlock();
+
+ return RemoveSize;
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+void WareSrcFileEditor::addMultilinePrefix(const QString& PrefixStr)
+{
+ SelectionData Data = createSelectionData();
+ Data.TextCursor.beginEditBlock();
+
+ Data.TextCursor.clearSelection();
+ QTextBlock CurrentBlock = Data.FirstBlock;
+ bool SelectionInWord = hasCharInBlockBeforePos(Data.StartPos);
+ int TotalUpdateSize = 0;
+
+ // Iterate through lines to indent
+ while (CurrentBlock.isValid() && (CurrentBlock.position() < (Data.EndPos + TotalUpdateSize)))
+ {
+ if(!CurrentBlock.text().isEmpty())
+ {
+ addLinePrefix(PrefixStr, CurrentBlock);
+ TotalUpdateSize += PrefixStr.length();
+ }
+ CurrentBlock = CurrentBlock.next();
+ }
+
+ // Restore selection
+ // Maintain anchor on indentation if the cursor is in leading spaces of first block
+ int StartPosUpdated = Data.StartPos + (SelectionInWord ? PrefixStr.size() : 0);
+ int EndPosUpdated = Data.EndPos + TotalUpdateSize;
+ //selectText(StartPosUpdated, EndPosUpdated, Data.CursorPos == Data.StartPos);
+ Data.CursorPos == Data.StartPos ? selectText(EndPosUpdated, StartPosUpdated) :
+ selectText(StartPosUpdated, EndPosUpdated);
+
+ Data.TextCursor.endEditBlock();
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
+void WareSrcFileEditor::removeMultilinePrefix(const QString& PrefixStr)
+{
+ SelectionData Data = createSelectionData();
+ Data.TextCursor.beginEditBlock();
+
+ Data.TextCursor.clearSelection();
+ QTextBlock CurrentBlock = Data.FirstBlock;
+ bool IsFirstBlock = true;
+ int RemoveSizeFirstBlock = 0;
+ int TotalUpdateSize = 0;
+
+ // Iterate through lines to indent
+ while (CurrentBlock.isValid() && (CurrentBlock.position() < (Data.EndPos - TotalUpdateSize)))
+ {
+ if(!CurrentBlock.text().isEmpty())
+ {
+ int RemoveSize = removeLinePrefix(PrefixStr, CurrentBlock);
+ TotalUpdateSize += RemoveSize;
+ if(IsFirstBlock)
+ {
+ // Accurate removing size of first block
+ RemoveSizeFirstBlock = RemoveSize;
+ IsFirstBlock = false;
+ }
+ }
+ CurrentBlock = CurrentBlock.next();
+ }
+
+ // Restore selection
+ int TempStartPosUpdated;
+ if(TotalUpdateSize == 0 || !hasCharInBlockBeforePos(Data.StartPos))
+ {
+ TempStartPosUpdated = Data.StartPos;
+ }
+ else
+ {
+ TempStartPosUpdated = Data.StartPos - RemoveSizeFirstBlock;
+ }
+ int StartPosUpdated = std::clamp(TempStartPosUpdated, Data.FirstBlock.position(), Data.EndPos);
+
+ int EndPosUpdated = Data.EndPos - TotalUpdateSize;
+ //selectText(StartPosUpdated, EndPosUpdated, Data.CursorPos == Data.StartPos);
+ Data.CursorPos == Data.StartPos ? selectText(EndPosUpdated, StartPosUpdated) :
+ selectText(StartPosUpdated, EndPosUpdated);
+
+ Data.TextCursor.endEditBlock();
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
void WareSrcFileEditor::insertCompletion()
{
QTextCursor Cursor = textCursor();
@@ -497,6 +808,11 @@ void WareSrcFileEditor::resizeEvent(QResizeEvent* Event)
void WareSrcFileEditor::contextMenuEvent(QContextMenuEvent* Event)
{
QMenu* Menu = createStandardContextMenu();
+
+ QAction *CommentingAction = new QAction("Comment/Uncomment", this);
+ CommentingAction->setShortcut(QKeySequence(tr("Ctrl+/")));
+ Menu->addAction(CommentingAction);
+ connect(CommentingAction, &QAction::triggered, this, &WareSrcFileEditor::handleSelectionCommenting);
Menu->addSeparator();
diff --git a/src/openfluid/ui/waresdev/WareSrcFileEditor.hpp b/src/openfluid/ui/waresdev/WareSrcFileEditor.hpp
index 9a71a8c26..53acffefa 100644
--- a/src/openfluid/ui/waresdev/WareSrcFileEditor.hpp
+++ b/src/openfluid/ui/waresdev/WareSrcFileEditor.hpp
@@ -41,9 +41,11 @@
#include
+#include
#include
#include
#include
+#include
#include
#include
@@ -191,6 +193,21 @@ class OPENFLUID_API WareSrcFileEditor: public QPlainTextEdit, public WareFileEdi
bool m_ShowLineMarkers = true;
+ struct SelectionData
+ {
+ QTextCursor TextCursor;
+
+ QTextBlock FirstBlock;
+
+ int CursorPos;
+
+ int StartPos;
+
+ int EndPos;
+ };
+
+ QString CommentExtraSpaceString = " ";
+
void writeString(const QString& Str, int InitialIndentInSpaceNb);
void insertNewLine();
@@ -199,6 +216,31 @@ class OPENFLUID_API WareSrcFileEditor: public QPlainTextEdit, public WareFileEdi
bool replaceString(const QString& StringToFind, const QString& StringForReplace, Qt::CaseSensitivity Cs);
+ int getLeadingSpacesCount(const QString& QStr);
+
+ bool hasCharInBlockBeforePos(int Pos);
+
+ void selectText(int StartPos, int EndPos);
+
+ SelectionData createSelectionData();
+
+ void addLinePrefix(const QString& PrefixStr, const QTextBlock& Block);
+
+ /**
+ Remove a prefix string from a block text. (It also removes one extra space (if found) after prefix string)
+ If the block text does not start with the prefix string (excluding spaces), it does nothing.
+ @param[in] PrefixStr The prefix string to remove from block text
+ @param[in] Block The block where to remove the prefix string
+ @return the number of characters removed from the block text
+ */
+ int removeLinePrefix(const QString& PrefixStr, const QTextBlock& Block);
+
+ void addMultilinePrefix(const QString& PrefixStr);
+
+ void removeMultilinePrefix(const QString& PrefixStr);
+
+ void handleSelectionCommenting();
+
protected:
diff --git a/src/openfluid/ui/waresdev/WareSrcFiletypeManager.cpp b/src/openfluid/ui/waresdev/WareSrcFiletypeManager.cpp
index 0f7dcb868..f5076201c 100644
--- a/src/openfluid/ui/waresdev/WareSrcFiletypeManager.cpp
+++ b/src/openfluid/ui/waresdev/WareSrcFiletypeManager.cpp
@@ -287,6 +287,7 @@ WareSrcFiletypeManager::HighlightingRules_t WareSrcFiletypeManager::parseSyntaxF
else if (TagName == "rule")
{
QString StyleName = QString::fromStdString(openfluid::thirdparty::getXMLAttribute(Elt,"style"));
+ QString RuleName = QString::fromStdString(openfluid::thirdparty::getXMLAttribute(Elt,"name"));
if (m_Formats.contains(StyleName))
{
@@ -304,6 +305,17 @@ WareSrcFiletypeManager::HighlightingRules_t WareSrcFiletypeManager::parseSyntaxF
if (!SimplePatternValue.isEmpty())
{
+ if(RuleName == "comment-singleline")
+ {
+ QRegularExpression CommentStringRegex(R"(^[^.]*)");
+ const auto& Match = CommentStringRegex.match(SimplePatternValue);
+ if (Match.hasMatch())
+ {
+ m_CommentStringByLangCode.insert(QString::fromStdString(
+ openfluid::thirdparty::getXMLAttribute(LangElt,"name")), Match.captured(0));
+ }
+ }
+
#if (QT_VERSION_MAJOR < 6)
Rules.append(HighlightingRule(StyleName, QRegExp(SimplePatternValue), Format));
#else
@@ -400,6 +412,25 @@ QString WareSrcFiletypeManager::getFileLanguage(const QString& FilePath) const
// =====================================================================
+QString WareSrcFiletypeManager::getFileCommentString(const QString& FilePath) const
+{
+ const auto It = m_CommentStringByLangCode.find(getFileLanguage(FilePath));
+
+ if (It != m_CommentStringByLangCode.end())
+ {
+ return It.value();
+ }
+ else
+ {
+ return QString();
+ }
+}
+
+
+// =====================================================================
+// =====================================================================
+
+
QMap WareSrcFiletypeManager::getIconsByFileExtensionList() const
{
return m_IconsByFileExtensionList;
diff --git a/src/openfluid/ui/waresdev/WareSrcFiletypeManager.hpp b/src/openfluid/ui/waresdev/WareSrcFiletypeManager.hpp
index 2de48a026..154b63797 100644
--- a/src/openfluid/ui/waresdev/WareSrcFiletypeManager.hpp
+++ b/src/openfluid/ui/waresdev/WareSrcFiletypeManager.hpp
@@ -47,9 +47,8 @@
#include
#if (QT_VERSION_MAJOR < 6)
#include
-#else
-#include
#endif
+#include
#include
#include
@@ -134,6 +133,8 @@ class OPENFLUID_API WareSrcFiletypeManager
QMap m_WareSrcFiletypes;
+ QMap m_CommentStringByLangCode;
+
WareSrcFiletypeManager();
~WareSrcFiletypeManager();
@@ -158,6 +159,8 @@ class OPENFLUID_API WareSrcFiletypeManager
QString getFileLanguage(const QString& FilePath) const;
+ QString getFileCommentString(const QString& FilePath) const;
+
};