Salome HOME
updated copyright message
[modules/gui.git] / tools / PyEditor / src / PyEditor_PyHighlighter.cxx
1 // Copyright (C) 2015-2023  OPEN CASCADE
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19 // File   : PyEditor_PyHighlighter.cxx
20 // Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
21 //
22
23 #include "PyEditor_PyHighlighter.h"
24
25 #include "PyEditor_Keywords.h"
26
27 #include <QSet>
28
29 #define NORMAL 0
30 #define TRIPLESINGLE 1
31 #define TRIPLEDOUBLE 2
32
33 /*!
34   \class PyEditor_PyHighlighter
35   \brief Python highlighter class defines the syntax highlighting rules.
36 */
37
38 PyEditor_PyHighlighter::TextBlockData::TextBlockData()
39 {
40 }
41
42 QVector<PyEditor_PyHighlighter::ParenthesisInfo*> PyEditor_PyHighlighter::TextBlockData::parentheses()
43 {
44   return myParentheses;
45 }
46
47 void PyEditor_PyHighlighter::TextBlockData::insert( PyEditor_PyHighlighter::ParenthesisInfo* theInfo )
48 {
49   int i = 0;
50   while ( i < myParentheses.size() && theInfo->position > myParentheses.at(i)->position )
51     ++i;
52
53   myParentheses.insert( i, theInfo );
54 }
55
56 /*!
57   \brief Constructor.
58   \param theDocument container for structured rich text documents.
59 */
60 PyEditor_PyHighlighter::PyEditor_PyHighlighter( QTextDocument* theDocument,
61                                                 PyEditor_Keywords* std,
62                                                 PyEditor_Keywords* user )
63   : QSyntaxHighlighter( theDocument ),
64     myStdKeywords( std ),
65     myUserKeywords( user )
66 {
67
68   connect(myStdKeywords, SIGNAL( keywordsChanged() ),
69           this, SLOT( onKeywordsChanged() ) );
70   connect(myUserKeywords, SIGNAL( keywordsChanged() ),
71           this, SLOT( onKeywordsChanged() ) );
72
73   updateHighlight();
74 }
75
76 void PyEditor_PyHighlighter::highlightBlock( const QString& theText )
77 {
78   TextBlockData* aData = new TextBlockData;
79   
80   insertBracketsData( RoundBrackets, aData, theText );
81   insertBracketsData( CurlyBrackets, aData, theText );
82   insertBracketsData( SquareBrackets, aData, theText );
83
84   setCurrentBlockUserData( aData );
85
86   foreach ( const HighlightingRule& rule, highlightingRules )
87   {
88     QRegExp expression( rule.pattern );
89     int anIndex = expression.indexIn( theText );
90     while ( anIndex >= 0 )
91     {
92       anIndex = expression.pos( rule.capture );
93       int aLength = expression.cap( rule.capture ).length();
94       setFormat( anIndex, aLength, rule.format );
95       anIndex = expression.indexIn( theText, anIndex + aLength );
96     }
97   }
98
99   setCurrentBlockState( NORMAL );
100
101   if ( theText.indexOf( tripleQuotesExpression ) != -1 )
102     return;
103
104   QList<int> aTripleSingle;
105   aTripleSingle << theText.indexOf( tripleSingleExpression ) << TRIPLESINGLE;
106
107   QList<int> aTripleDouble;
108   aTripleDouble << theText.indexOf( tripleDoubleExpression ) << TRIPLEDOUBLE;
109  
110   QList< QList<int> > aTripleExpressions;
111   aTripleExpressions << aTripleSingle << aTripleDouble;
112
113   for ( int i = 0; i < aTripleExpressions.length(); i++ )
114   {
115     QList<int> aBlock = aTripleExpressions[i];
116     int anIndex = aBlock[0];
117     int aState = aBlock[1];
118     if ( previousBlockState() == aState )
119     {
120       if ( anIndex == -1 )
121       {
122         anIndex = theText.length();
123         setCurrentBlockState( aState );
124       }
125       setFormat( 0, anIndex + 3, multiLineCommentFormat );
126     }
127     else if ( anIndex > -1 )
128     {
129       setCurrentBlockState( aState );
130       setFormat( anIndex, theText.length(), multiLineCommentFormat );
131     }
132   }
133 }
134
135 void PyEditor_PyHighlighter::insertBracketsData( char theLeftSymbol,
136                                                  char theRightSymbol,
137                                                  TextBlockData* theData,
138                                                  const QString& theText )
139 {
140   int leftPosition = theText.indexOf( theLeftSymbol );
141   while( leftPosition != -1 )
142   {
143     ParenthesisInfo* info = new ParenthesisInfo();
144     info->character = theLeftSymbol;
145     info->position = leftPosition;
146
147     theData->insert( info );
148     leftPosition = theText.indexOf( theLeftSymbol, leftPosition + 1 );
149   }
150
151   int rightPosition = theText.indexOf( theRightSymbol );
152   while( rightPosition != -1 )
153   {
154     ParenthesisInfo* info = new ParenthesisInfo();
155     info->character = theRightSymbol;
156     info->position = rightPosition;
157
158     theData->insert( info );
159     rightPosition = theText.indexOf( theRightSymbol, rightPosition + 1 );
160   }
161 }
162
163 void PyEditor_PyHighlighter::insertBracketsData( Brackets theBrackets,
164                                                  TextBlockData* theData,
165                                                  const QString& theText )
166 {
167   char leftChar = '0';
168   char rightChar = '0';
169   
170   switch( theBrackets )
171   {
172   case RoundBrackets:
173     leftChar = '(';
174     rightChar = ')';
175     break;
176   case CurlyBrackets:
177     leftChar = '{';
178     rightChar = '}';
179     break;
180   case SquareBrackets:
181     leftChar = '[';
182     rightChar = ']';
183     break;
184   }
185
186   insertBracketsData( leftChar, rightChar, theData, theText );
187 }
188
189 void PyEditor_PyHighlighter::onKeywordsChanged()
190 {
191   updateHighlight();
192   rehighlight();
193 }
194
195 void PyEditor_PyHighlighter::updateHighlight()
196 {
197   highlightingRules.clear();
198
199   HighlightingRule aRule;
200
201   QList<PyEditor_Keywords*> dictList;
202   dictList << myStdKeywords << myUserKeywords;
203
204   // Keywords
205   QSet<QString> existing;
206   for ( QList<PyEditor_Keywords*>::const_iterator it = dictList.begin();
207         it != dictList.end(); ++it ) {
208     PyEditor_Keywords* kwDict = *it;
209     QList<QColor> colors = kwDict->colors();
210     for ( QList<QColor>::const_iterator itr = colors.begin(); itr != colors.end(); ++itr ) {
211       QColor color = *itr;
212       QTextCharFormat format;
213       format.setForeground( color );
214       QStringList keywords = kwDict->keywords( color );
215       foreach ( const QString& keyword, keywords ) {
216         if ( existing.contains( keyword ) )
217           continue;
218
219         aRule.pattern = QRegExp( QString( "\\b%1\\b" ).arg( keyword ) );
220         aRule.format = format;
221         aRule.capture = 0;
222         highlightingRules.append( aRule );
223         existing.insert( keyword );
224       }
225     }
226   }
227
228   // Reference to the current instance of the class
229   referenceClassFormat.setForeground( QColor( 179, 143, 0 ) );
230   referenceClassFormat.setFontItalic( true );
231   aRule.pattern = QRegExp( "\\bself\\b" );
232   aRule.format = referenceClassFormat;
233   aRule.capture = 0;
234   highlightingRules.append( aRule );
235
236   // Numbers
237   numberFormat.setForeground( Qt::darkMagenta );
238   aRule.pattern = QRegExp( "\\b([-+])?(\\d+(\\.)?\\d*|\\d*(\\.)?\\d+)(([eE]([-+])?)?\\d+)?\\b" );
239   aRule.format = numberFormat;
240   aRule.capture = 0;
241   highlightingRules.append( aRule );
242
243   // String qoutation
244   quotationFormat.setForeground( Qt::darkGreen );
245   aRule.pattern = QRegExp( "(?:'[^']*'|\"[^\"]*\")" );
246   aRule.pattern.setMinimal( true );
247   aRule.format = quotationFormat;
248   aRule.capture = 0;
249   highlightingRules.append( aRule );
250
251   // Function names
252   functionFormat.setFontWeight( QFont::Bold );
253   aRule.pattern = QRegExp( "(?:def\\s*)(\\b[A-Za-z0-9_]+)(?=[\\W])" );
254   aRule.capture = 1;
255   aRule.format = functionFormat;
256   highlightingRules.append( aRule );
257
258   // Class names
259   classFormat.setForeground( Qt::darkBlue );
260   classFormat.setFontWeight( QFont::Bold );
261   aRule.pattern = QRegExp( "(?:class\\s*)(\\b[A-Za-z0-9_]+)(?=[\\W])" );
262   aRule.capture = 1;
263   aRule.format = classFormat;
264   highlightingRules.append( aRule );
265
266   // Multi line comments
267   multiLineCommentFormat.setForeground( Qt::darkRed );
268   tripleQuotesExpression = QRegExp( "(:?\"[\"]\".*\"[\"]\"|'''.*''')" );
269   aRule.pattern = tripleQuotesExpression;
270   aRule.pattern.setMinimal( true );
271   aRule.format = multiLineCommentFormat;
272   aRule.capture = 0;
273   highlightingRules.append( aRule );
274
275   tripleSingleExpression = QRegExp( "'''(?!\")" );
276   tripleDoubleExpression = QRegExp( "\"\"\"(?!')" );
277
278   // Single comments
279   singleLineCommentFormat.setForeground( Qt::darkGray );
280   aRule.pattern = QRegExp( "#[^\n]*" );
281   aRule.format = singleLineCommentFormat;
282   aRule.capture = 0;
283   highlightingRules.append( aRule );
284 }