Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How to highlight multiline text on a QTextEdit?

How to highlight multiline text on a QTextEdit?

Scheduled Pinned Locked Moved Unsolved General and Desktop
5 Posts 3 Posters 677 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    Daniella
    wrote on last edited by Daniella
    #1

    I'm trying to highlight multiline comments on a QTextEdit, I searched around for how this is done on Qt Designer (in the QTextEdit when you are editing a stylesheet multiline comments are highlighted)

    I found a class named HtmlHighlighter at msvc2019_64\include\QtGui\qsyntaxhighlighter.h (not sure if this is the correct class used by Designer)

    when I set it on the first QTextEdit of the QMainWindow nothing is highlighted, I'm missing something?

    Based on the HtmlHighlighter class, I have written the MyHtmlHighlighter class, the problem is it only highlights comments that are on the same line:

    enter image description here

    How i could get multiline comments highlighted in a QTextEdit?

    #include "htmlhighlighter.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        QList<QTextEdit*> textEditList = w.findChildren<QTextEdit*>();
    
        HtmlHighlighter* highlighter = new HtmlHighlighter(textEditList[0]);
        MyHtmlHighlighter* highlighter_2 = new MyHtmlHighlighter(textEditList[1]);
        return a.exec();
    }
    

    mainwindow

    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow())
    {
        ui->setupUi(this);
    
        QString text = QString(R"(/*abc*/ 
    /* 
    abc
     */
    )");
        QTextEdit* textEdit = new QTextEdit(this);
        QTextEdit* textEdit_2 = new QTextEdit(this);
        QVBoxLayout* layout = new QVBoxLayout(ui->centralWidget);
        layout->addWidget(textEdit);
        layout->addWidget(textEdit_2);
    
        textEdit->setText(text);
        textEdit_2->setText(text);
    }
    

    htmlhighlighter

    class MyHtmlHighlighter : public QSyntaxHighlighter
    {
    public:
        MyHtmlHighlighter(QWidget* parent) : QSyntaxHighlighter(parent)
        {
            commentFormat.setForeground(QColor("#29ff3d"));
            commentFormat.setFontItalic(true);
            highlightingRules.append({QRegularExpression("/\\*(.|\\n)*?\\*/"), commentFormat});
        };
    
    protected:
    	void highlightBlock(const QString& text) override
        {
            for (const HighlightingRule& rule : highlightingRules)
            {
                QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
    
                while (matchIterator.hasNext())
                {
                    QRegularExpressionMatch match = matchIterator.next();
                    setFormat(match.capturedStart(), match.capturedLength(), rule.format);
                }
            }
        }
    
    private:
        struct HighlightingRule
        {
            QRegularExpression pattern;
            QTextCharFormat format;
        };
    	QList<HighlightingRule> highlightingRules;
        QTextCharFormat commentFormat;
    };
    
    
    // extracted from: msvc2019_64\include\QtGui\qsyntaxhighlighter.h
    using namespace Qt::StringLiterals;
    
    class HtmlHighlighter : public QSyntaxHighlighter
    {
        Q_OBJECT
    public:
        enum Construct
        {
            Entity,
            Tag,
            Comment,
            Attribute,
            Value,
            LastConstruct = Value
        };
    
        HtmlHighlighter(QTextEdit* textEdit) : QSyntaxHighlighter(textEdit->document())
        {
            QTextCharFormat entityFormat;
            entityFormat.setForeground(Qt::red);
            setFormatFor(Entity, entityFormat);
    
            QTextCharFormat tagFormat;
            tagFormat.setForeground(Qt::darkMagenta);
            tagFormat.setFontWeight(QFont::Bold);
            setFormatFor(Tag, tagFormat);
    
            QTextCharFormat commentFormat;
            commentFormat.setForeground(Qt::gray);
            commentFormat.setFontItalic(true);
            setFormatFor(Comment, commentFormat);
    
            QTextCharFormat attributeFormat;
            attributeFormat.setForeground(Qt::black);
            attributeFormat.setFontWeight(QFont::Bold);
            setFormatFor(Attribute, attributeFormat);
    
            QTextCharFormat valueFormat;
            valueFormat.setForeground(Qt::blue);
            setFormatFor(Value, valueFormat);
        }
    
        void setFormatFor(Construct construct, const QTextCharFormat& format)
        {
            m_formats[construct] = format;
            rehighlight();
        }
    
        QTextCharFormat formatFor(Construct construct) const
        {
            return m_formats[construct];
        }
    
    protected:
        enum State
        {
            NormalState = -1,
            InComment,
            InTag
        };
    
        void highlightBlock(const QString& text) override
        {
            static const QChar tab = u'\t';
            static const QChar space = u' ';
    
            int state = previousBlockState();
            qsizetype len = text.size();
            qsizetype start = 0;
            qsizetype pos = 0;
    
            while (pos < len) {
                switch (state) {
                case NormalState:
                default:
                while (pos < len) {
                    QChar ch = text.at(pos);
                    if (ch == u'<') {
                        if (QStringView{text}.sliced(pos).startsWith("<!--"_L1)) {
                            state = InComment;
                        }
                        else {
                            state = InTag;
                            start = pos;
                            while (pos < len && text.at(pos) != space
                                && text.at(pos) != u'>'
                                && text.at(pos) != tab
                                && !QStringView{text}.sliced(pos).startsWith("/>"_L1)) {
                                ++pos;
                            }
                            if (QStringView{text}.sliced(pos).startsWith("/>"_L1))
                                ++pos;
                            setFormat(start, pos - start,
                                m_formats[Tag]);
                            break;
                        }
                        break;
                    }
                    if (ch == u'&') {
                        start = pos;
                        while (pos < len && text.at(pos++) != u';')
                            ;
                        setFormat(start, pos - start,
                            m_formats[Entity]);
                    }
                    else {
                        // No tag, comment or entity started, continue...
                        ++pos;
                    }
                }
                break;
                case InComment:
                start = pos;
                for (; pos < len; ++pos) {
                    if (QStringView{text}.sliced(pos).startsWith("-->"_L1)) {
                        pos += 3;
                        state = NormalState;
                        break;
                    }
                }
                setFormat(start, pos - start, m_formats[Comment]);
                break;
                case InTag:
                QChar quote = QChar::Null;
                while (pos < len) {
                    QChar ch = text.at(pos);
                    if (quote.isNull()) {
                        start = pos;
                        if (ch == '\''_L1 || ch == u'"') {
                            quote = ch;
                        }
                        else if (ch == u'>') {
                            ++pos;
                            setFormat(start, pos - start, m_formats[Tag]);
                            state = NormalState;
                            break;
                        }
                        else if (QStringView{text}.sliced(pos).startsWith("/>"_L1)) {
                            pos += 2;
                            setFormat(start, pos - start, m_formats[Tag]);
                            state = NormalState;
                            break;
                        }
                        else if (ch != space && text.at(pos) != tab) {
                            // Tag not ending, not a quote and no whitespace, so
                            // we must be dealing with an attribute.
                            ++pos;
                            while (pos < len && text.at(pos) != space
                                && text.at(pos) != tab
                                && text.at(pos) != u'=')
                                ++pos;
                            setFormat(start, pos - start, m_formats[Attribute]);
                            start = pos;
                        }
                    }
                    else if (ch == quote) {
                        quote = QChar::Null;
    
                        // Anything quoted is a value
                        setFormat(start, pos - start, m_formats[Value]);
                    }
                    ++pos;
                }
                break;
                }
            }
            setCurrentBlockState(state);
        }
    
    private:
        QTextCharFormat m_formats[LastConstruct + 1];
    };
    
    SGaistS 1 Reply Last reply
    0
    • D Daniella

      I'm trying to highlight multiline comments on a QTextEdit, I searched around for how this is done on Qt Designer (in the QTextEdit when you are editing a stylesheet multiline comments are highlighted)

      I found a class named HtmlHighlighter at msvc2019_64\include\QtGui\qsyntaxhighlighter.h (not sure if this is the correct class used by Designer)

      when I set it on the first QTextEdit of the QMainWindow nothing is highlighted, I'm missing something?

      Based on the HtmlHighlighter class, I have written the MyHtmlHighlighter class, the problem is it only highlights comments that are on the same line:

      enter image description here

      How i could get multiline comments highlighted in a QTextEdit?

      #include "htmlhighlighter.h"
      
      int main(int argc, char *argv[])
      {
          QApplication a(argc, argv);
          MainWindow w;
          w.show();
      
          QList<QTextEdit*> textEditList = w.findChildren<QTextEdit*>();
      
          HtmlHighlighter* highlighter = new HtmlHighlighter(textEditList[0]);
          MyHtmlHighlighter* highlighter_2 = new MyHtmlHighlighter(textEditList[1]);
          return a.exec();
      }
      

      mainwindow

      MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow())
      {
          ui->setupUi(this);
      
          QString text = QString(R"(/*abc*/ 
      /* 
      abc
       */
      )");
          QTextEdit* textEdit = new QTextEdit(this);
          QTextEdit* textEdit_2 = new QTextEdit(this);
          QVBoxLayout* layout = new QVBoxLayout(ui->centralWidget);
          layout->addWidget(textEdit);
          layout->addWidget(textEdit_2);
      
          textEdit->setText(text);
          textEdit_2->setText(text);
      }
      

      htmlhighlighter

      class MyHtmlHighlighter : public QSyntaxHighlighter
      {
      public:
          MyHtmlHighlighter(QWidget* parent) : QSyntaxHighlighter(parent)
          {
              commentFormat.setForeground(QColor("#29ff3d"));
              commentFormat.setFontItalic(true);
              highlightingRules.append({QRegularExpression("/\\*(.|\\n)*?\\*/"), commentFormat});
          };
      
      protected:
      	void highlightBlock(const QString& text) override
          {
              for (const HighlightingRule& rule : highlightingRules)
              {
                  QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
      
                  while (matchIterator.hasNext())
                  {
                      QRegularExpressionMatch match = matchIterator.next();
                      setFormat(match.capturedStart(), match.capturedLength(), rule.format);
                  }
              }
          }
      
      private:
          struct HighlightingRule
          {
              QRegularExpression pattern;
              QTextCharFormat format;
          };
      	QList<HighlightingRule> highlightingRules;
          QTextCharFormat commentFormat;
      };
      
      
      // extracted from: msvc2019_64\include\QtGui\qsyntaxhighlighter.h
      using namespace Qt::StringLiterals;
      
      class HtmlHighlighter : public QSyntaxHighlighter
      {
          Q_OBJECT
      public:
          enum Construct
          {
              Entity,
              Tag,
              Comment,
              Attribute,
              Value,
              LastConstruct = Value
          };
      
          HtmlHighlighter(QTextEdit* textEdit) : QSyntaxHighlighter(textEdit->document())
          {
              QTextCharFormat entityFormat;
              entityFormat.setForeground(Qt::red);
              setFormatFor(Entity, entityFormat);
      
              QTextCharFormat tagFormat;
              tagFormat.setForeground(Qt::darkMagenta);
              tagFormat.setFontWeight(QFont::Bold);
              setFormatFor(Tag, tagFormat);
      
              QTextCharFormat commentFormat;
              commentFormat.setForeground(Qt::gray);
              commentFormat.setFontItalic(true);
              setFormatFor(Comment, commentFormat);
      
              QTextCharFormat attributeFormat;
              attributeFormat.setForeground(Qt::black);
              attributeFormat.setFontWeight(QFont::Bold);
              setFormatFor(Attribute, attributeFormat);
      
              QTextCharFormat valueFormat;
              valueFormat.setForeground(Qt::blue);
              setFormatFor(Value, valueFormat);
          }
      
          void setFormatFor(Construct construct, const QTextCharFormat& format)
          {
              m_formats[construct] = format;
              rehighlight();
          }
      
          QTextCharFormat formatFor(Construct construct) const
          {
              return m_formats[construct];
          }
      
      protected:
          enum State
          {
              NormalState = -1,
              InComment,
              InTag
          };
      
          void highlightBlock(const QString& text) override
          {
              static const QChar tab = u'\t';
              static const QChar space = u' ';
      
              int state = previousBlockState();
              qsizetype len = text.size();
              qsizetype start = 0;
              qsizetype pos = 0;
      
              while (pos < len) {
                  switch (state) {
                  case NormalState:
                  default:
                  while (pos < len) {
                      QChar ch = text.at(pos);
                      if (ch == u'<') {
                          if (QStringView{text}.sliced(pos).startsWith("<!--"_L1)) {
                              state = InComment;
                          }
                          else {
                              state = InTag;
                              start = pos;
                              while (pos < len && text.at(pos) != space
                                  && text.at(pos) != u'>'
                                  && text.at(pos) != tab
                                  && !QStringView{text}.sliced(pos).startsWith("/>"_L1)) {
                                  ++pos;
                              }
                              if (QStringView{text}.sliced(pos).startsWith("/>"_L1))
                                  ++pos;
                              setFormat(start, pos - start,
                                  m_formats[Tag]);
                              break;
                          }
                          break;
                      }
                      if (ch == u'&') {
                          start = pos;
                          while (pos < len && text.at(pos++) != u';')
                              ;
                          setFormat(start, pos - start,
                              m_formats[Entity]);
                      }
                      else {
                          // No tag, comment or entity started, continue...
                          ++pos;
                      }
                  }
                  break;
                  case InComment:
                  start = pos;
                  for (; pos < len; ++pos) {
                      if (QStringView{text}.sliced(pos).startsWith("-->"_L1)) {
                          pos += 3;
                          state = NormalState;
                          break;
                      }
                  }
                  setFormat(start, pos - start, m_formats[Comment]);
                  break;
                  case InTag:
                  QChar quote = QChar::Null;
                  while (pos < len) {
                      QChar ch = text.at(pos);
                      if (quote.isNull()) {
                          start = pos;
                          if (ch == '\''_L1 || ch == u'"') {
                              quote = ch;
                          }
                          else if (ch == u'>') {
                              ++pos;
                              setFormat(start, pos - start, m_formats[Tag]);
                              state = NormalState;
                              break;
                          }
                          else if (QStringView{text}.sliced(pos).startsWith("/>"_L1)) {
                              pos += 2;
                              setFormat(start, pos - start, m_formats[Tag]);
                              state = NormalState;
                              break;
                          }
                          else if (ch != space && text.at(pos) != tab) {
                              // Tag not ending, not a quote and no whitespace, so
                              // we must be dealing with an attribute.
                              ++pos;
                              while (pos < len && text.at(pos) != space
                                  && text.at(pos) != tab
                                  && text.at(pos) != u'=')
                                  ++pos;
                              setFormat(start, pos - start, m_formats[Attribute]);
                              start = pos;
                          }
                      }
                      else if (ch == quote) {
                          quote = QChar::Null;
      
                          // Anything quoted is a value
                          setFormat(start, pos - start, m_formats[Value]);
                      }
                      ++pos;
                  }
                  break;
                  }
              }
              setCurrentBlockState(state);
          }
      
      private:
          QTextCharFormat m_formats[LastConstruct + 1];
      };
      
      SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      Shouldn't you rather use \\s so it includes all white space types ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      D 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi,

        Shouldn't you rather use \\s so it includes all white space types ?

        D Offline
        D Offline
        Daniella
        wrote on last edited by
        #3

        @SGaist do you mean in the QRegularExpression? i tried it, same result.
        -edit-
        I noticed that the text is received "splitted" in the the highlightBlock function, when its

        /*
        abc
        */
        

        text is /* then abc then */, this is why the regex is working only when its all on the same line.

        I don't understand why the HtmlHighlighter class copied from the mentioned file isn't working.

        SGaistS 1 Reply Last reply
        0
        • D Daniella

          @SGaist do you mean in the QRegularExpression? i tried it, same result.
          -edit-
          I noticed that the text is received "splitted" in the the highlightBlock function, when its

          /*
          abc
          */
          

          text is /* then abc then */, this is why the regex is working only when its all on the same line.

          I don't understand why the HtmlHighlighter class copied from the mentioned file isn't working.

          SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          I realized, you are over complicating things, take a look at the QSyntaxHighlighter documentation. It provides an example for doing exactly what you want.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          1
          • C Offline
            C Offline
            CPPUIX
            wrote on last edited by CPPUIX
            #5

            For the sake of availability and saving someone's time, I'll share my solution again here.


            There's a very good and detailed Syntax Highlighter Example in the Qt Documentation, with a section that explains how to deal with multiline comments explained in Highlighter class implementation section.

            Here's my own extraction of it:

            the multiline comment needs special care due to the design of the QSyntaxHighlighter class.

            After a QSyntaxHighlighter object is created, its highlightBlock() function will be called automatically whenever it is necessary by the rich text engine, highlighting the given text block. The problem appears when a comment spans several text blocks. We will take a closer look at how this problem can be solved when reviewing the implementation of the Highlighter::highlightBlock() function. At this point we only specify the multiline comment's color.

            setCurrentBlockState(0);
            

            To deal with constructs that can span several text blocks (like the C++ multiline comment), it is necessary to know the end state of the previous text block (e.g. "in comment"). Inside your highlightBlock() implementation you can query the end state of the previous text block using the QSyntaxHighlighter::previousBlockState() function. After parsing the block you can save the last state using QSyntaxHighlighter::setCurrentBlockState().

            The previousBlockState() function return an int value. If no state is set, the returned value is -1. You can designate any other value to identify any given state using the setCurrentBlockState() function. Once the state is set, the QTextBlock keeps that value until it is set again or until the corresponding paragraph of text is deleted.

            In this example we have chosen to use 0 to represent the "not in comment" state, and 1 for the "in comment" state. When the stored syntax highlighting rules are applied we initialize the current block state to 0.

            int startIndex = 0;
            if (previousBlockState() != 1)
               startIndex = text.indexOf(commentStartExpression);
            

            If the previous block state was "in comment" (previousBlockState() == 1), we start the search for an end expression at the beginning of the text block. If the previousBlockState() returns 0, we start the search at the location of the first occurrence of a start expression.

            while (startIndex >= 0) {
                QRegularExpressionMatch match = commentEndExpression.match(text, startIndex);
                int endIndex = match.capturedStart();
                int commentLength = 0;
                if (endIndex == -1) {
                   setCurrentBlockState(1);
                   commentLength = text.length() - startIndex;
                } else {
                   commentLength = endIndex - startIndex
                                      + match.capturedLength();
                }
                setFormat(startIndex, commentLength, multiLineCommentFormat);
                startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
            }
            

            When an end expression is found, we calculate the length of the comment and apply the multiline comment format. Then we search for the next occurrence of the start expression and repeat the process. If no end expression can be found in the current text block we set the current block state to 1, i.e. "in comment".

            TL;DR: QSyntaxHighlighter only formats one text block at a time, a multiline comment is formed of several ones, hence why you are only able to format single line comments.

            Based on that, I changed MyHtmlHighlighter as follows:

            class MyHtmlHighlighter : public QSyntaxHighlighter
            {
            public:
                MyHtmlHighlighter(QWidget* parent) : QSyntaxHighlighter(parent)
                {
                    commentFormat.setForeground(QColor("#29ff3d"));
                    commentFormat.setFontItalic(true);
                    commentStartExpression = QRegularExpression(QStringLiteral("/\\*"));
                    commentEndExpression = QRegularExpression(QStringLiteral("\\*/"));
                }
            
            protected:
                void highlightBlock(const QString& text) override
                {
                    //useless for multiline comment formatting
                    for (const HighlightingRule& rule : highlightingRules)
                    {
                        QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
            
                        while (matchIterator.hasNext())
                        {
                            QRegularExpressionMatch match = matchIterator.next();
                            setFormat(match.capturedStart(), match.capturedLength(), rule.format);
                        }
                    }
            
                    //this is where multiline comments processing starts
                    setCurrentBlockState(0);
            
                    int startIndex = 0;
                    if (previousBlockState() != 1)
                        startIndex = text.indexOf(commentStartExpression);
            
                    while (startIndex >= 0)
                    {
                        QRegularExpressionMatch match = commentEndExpression.match(text, startIndex);
                        int endIndex = match.capturedStart();
                        int commentLength = 0;
            
                        if (endIndex == -1)
                        {
                            setCurrentBlockState(1);
                            commentLength = text.length() - startIndex;
                        }
                        else
                        {
                            commentLength = endIndex - startIndex
                                            + match.capturedLength();
                        }
            
                        setFormat(startIndex, commentLength, commentFormat);
                        startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
                    }
                }
            
            private:
                struct HighlightingRule
                {
                    QRegularExpression pattern;
                    QTextCharFormat format;
                };
                QList<HighlightingRule> highlightingRules;
                QTextCharFormat commentFormat;
                //added
                QRegularExpression commentStartExpression;
                QRegularExpression commentEndExpression;
            };
            

            Here's a test:

            multiline formatting

            1 Reply Last reply
            1

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved