1 /* File: "guideuihighlighterscheme.cpp", Time-stamp: <2005-04-28 12:02:08 feeley> */
2 
3 /* Copyright (C) 1994-2005 by Marc Feeley, All Rights Reserved. */
4 
5 /*---------------------------------------------------------------------------*/
6 
7 #include "guide.h"
8 #include "guideuihighlighterscheme.h"
9 #include <qtextedit.h>
10 #include <qvaluestack.h>
11 
12 /*---------------------------------------------------------------------------*/
13 
14 GuideUiCodeFormat* GuideUiHighlighterScheme::schemeCodeFormat = 0;
15 
GuideUiHighlighterScheme(QTextEdit * textEdit)16 GuideUiHighlighterScheme::GuideUiHighlighterScheme (QTextEdit *textEdit)
17   : GuideUiHighlighter (textEdit)
18 {
19   if (schemeCodeFormat == 0)
20     {
21       // Add the default format elements to the code format
22       schemeCodeFormat = codeFormat = new GuideUiCodeFormat();
23       codeFormat->addElement(QColor(0  ,0  ,0  ), "Normal");
24       codeFormat->addElement(QColor(128,0  ,128), "Parenthesis");
25       codeFormat->addElement(QColor(128,128,128), "Comment");
26       codeFormat->addElement(QColor(0  ,0  ,255), "Number");
27       codeFormat->addElement(QColor(255,0  ,0  ), "String");
28       codeFormat->addElement(QColor(0  ,128,0  ), "Identifier");
29       codeFormat->addElement(QColor(128,128,0  ), "Keyword");
30       codeFormat->addElement(QColor(0  ,128,128), "Constant");
31       codeFormat->addElement(QColor(255,128,255), "Highlight");
32     }
33   else
34     codeFormat = schemeCodeFormat;
35   highlightPara[0] = highlightPara[1] = -1;
36 }
37 
38 // Create a syntax highlighter to apply on 'text'
applyTo(QTextEdit * text)39 void GuideUiHighlighterScheme::applyTo (QTextEdit *text)
40 {
41   new GuideUiHighlighterScheme(text);
42 }
43 
44 // Returns the length of the pointed token
tokenLength(int para,int index)45 int GuideUiHighlighterScheme::tokenLength (int para, int index)
46 {
47   QValueVector<TokenType> *tokens;
48   QString text = textEdit()->text(para);
49   int endState = getEndState(para);
50   tokens = getTokens(text, endState);
51   TokenType t;
52 
53   // Search for the pointed token
54   int tokenNb = -1;
55   for (uint i=0; i<tokens->size(); i++)
56     {
57       t = (*tokens)[i];
58       if (index >= t.start && index < t.start + t.length)
59         {
60           tokenNb = i;
61           break;
62         }
63     }
64 
65   if (tokenNb == -1) return 1;
66   t = (*tokens)[tokenNb];
67   if (t.type != T_OPEN_PARENT)
68     return t.length;
69 
70   // If there is a parenthesis
71   int parent = 1;
72   tokenNb++;
73   while ((uint)tokenNb < tokens->size() && parent > 0)
74     {
75       t = (*tokens)[tokenNb];
76       if (t.type == T_OPEN_PARENT)
77         parent++;
78       if (t.type == T_CLOSE_PARENT)
79         parent--;
80 
81       tokenNb++;
82     }
83 
84   if (parent == 0)
85     {
86       t = (*tokens)[--tokenNb];
87       return t.start + t.length - index;
88     }
89   return text.length() - index;
90 }
91 
highlightParagraph(const QString & text,int endStateOfLastPara)92 int GuideUiHighlighterScheme::highlightParagraph (const QString &text, int endStateOfLastPara)
93 {
94   if (!schemeCodeFormat->highlighterEnabled)
95     {
96       // No highlighting
97       setFormat (0, text.length (), 0);
98       return 0;
99     }
100 
101   QString text2 = text;
102 
103   // If it is a console, alter 'text' to highlight only the typed text
104   if (consoleInfo)
105     {
106       int startCol;
107       if (currentParagraph() == textEdit()->paragraphs()-1)
108         startCol = consoleInfo->nbReadOnlyChars;
109       else
110         startCol = consoleInfo->getStartCol(currentParagraph());
111       if (startCol == -1)
112         {
113           text2 = "";
114           setFormat(0, text.length(), 0);
115         }
116       else
117         {
118           text2 = QString().fill(' ',startCol);
119           text2 += text.right(text.length()-startCol);
120           setFormat(0, startCol, 0);
121         }
122     }
123 
124   QValueVector<TokenType> *tokens;
125   tokens = getTokens(text2, endStateOfLastPara);
126   TokenType t;
127 
128   // Search for the first caracter to highlight in the current paragraph
129   int highlightIndex = 0;
130   while (highlightIndex < 2 && currentParagraph() != highlightPara[highlightIndex])
131     highlightIndex++;
132 
133   for (uint i=0; i<tokens->size(); i++)
134     {
135       t = (*tokens)[i];
136       int colorNb;
137       switch(t.type)
138         {
139         case T_IDENT:
140           colorNb = 5; break;
141         case T_KEYWORD:
142           colorNb = 6; break;
143         case T_OPEN_PARENT: case T_CLOSE_PARENT:
144           colorNb = 1; break;
145         case T_NUMBER:
146           colorNb = 3; break;
147         case T_STRING:
148           colorNb = 4; break;
149         case T_OPEN_COMMENT: case T_CLOSE_COMMENT: case T_COMMENT:
150           colorNb = 2; break;
151         case T_CONSTANT:
152           colorNb = 7; break;
153         default:
154           colorNb = 0; break;
155         }
156       setFormat(t.start, t.length, colorNb);
157 
158       // Highlight designated characters
159       if (highlightIndex < 2) {
160         if (highlightColumn[highlightIndex] >= t.start && highlightColumn[highlightIndex] < t.start + t.length)
161           {
162             setFormat(highlightColumn[highlightIndex], 1, 8);
163             highlightIndex++;
164             while (highlightIndex < 2 && currentParagraph() != highlightPara[highlightIndex])
165               highlightIndex++;
166           }
167       }
168     }
169 
170   delete tokens;
171 
172   setEndState(currentParagraph(), endStateOfLastPara);
173   return endStateOfLastPara;
174 }
175 
176 // Get a list of syntaxic tokens from paragraph Text
getTokens(const QString & text,int & endStateOfLastPara)177 QValueVector<GuideUiHighlighterScheme::TokenType>* GuideUiHighlighterScheme::getTokens (const QString &text, int &endStateOfLastPara)
178 {
179   QValueVector<TokenType> *tokens = new QValueVector<TokenType>;
180   TokenType t;
181   int current = 0;
182   int length = text.length();
183   if (endStateOfLastPara == S_FIRST_PARA)
184     endStateOfLastPara = S_NORMAL;
185 
186   // Find all the tokens in this paragraph
187   while (current < length)
188     {
189       int last = current;
190       t.type = T_NO_TOKEN;
191 
192       if (endStateOfLastPara == S_IN_STRING || endStateOfLastPara == S_IN_STRING_2)
193         {
194           while ((last < length) && (text[last] != ((endStateOfLastPara == S_IN_STRING)?'\"':'|')))
195             {
196               if (text[last] == '\\') last++;
197               last++;
198             }
199           if (last >= length) // Non-terminated string
200             last = length;
201           else
202             endStateOfLastPara = S_NORMAL;
203           t.type = T_STRING;
204         }
205       // If it is on the comment state
206       else if (endStateOfLastPara >= S_COMMENT)
207         {
208           int index1 = text.find("|#", current);
209           int index2 = text.find("#|", current);
210           if (index1 == -1 && index2 == -1)
211             {
212               last = length-1;
213               t.type = T_COMMENT;
214             }
215           else if (index1 == current)
216             {
217               last = current+1;
218               t.type = T_CLOSE_COMMENT;
219               endStateOfLastPara--;
220               if (endStateOfLastPara < S_COMMENT)
221                 endStateOfLastPara = S_NORMAL;
222             }
223           else if (index2 == current)
224             {
225               last = current+1;
226               t.type = T_OPEN_COMMENT;
227               endStateOfLastPara++;
228             }
229           else if (index1 == -1)
230             {
231               last = index2-1;
232               t.type = T_COMMENT;
233             }
234           else if (index2 == -1)
235             {
236               last = index1-1;
237               t.type = T_COMMENT;
238             }
239           else
240             {
241               last = min(index1, index2)-1;
242               t.type = T_COMMENT;
243             }
244         }
245       else
246         {
247           char c = getCharType(text[current]);
248 
249           // Open parenthesis
250           if (text[last] == '(' ||
251               text[last] == '{' ||
252               text[last] == '[')
253             {
254               t.type = T_OPEN_PARENT;
255             }
256           // Close parenthesis
257           else if (text[last] == ')' ||
258                    text[last] == '}' ||
259                    text[last] == ']')
260             {
261               t.type = T_CLOSE_PARENT;
262             }
263           // String
264           else if (text[last] == '\"' ||
265                    text[last] == '|')
266             {
267               last++;
268               while (last < length && text[current] != text[last])
269                 {
270                   if (text[last] == '\\') last++;
271                   last++;
272                 }
273               if (last >= length) // Non-terminated string
274                 {
275                   last = length;
276                   endStateOfLastPara = (text[last]=='\"')?S_IN_STRING:S_IN_STRING_2;
277                 }
278               t.type = T_STRING;
279             }
280           // Line comment
281           else if (text[last] == ';')
282             {
283               last = length;
284               t.type = T_COMMENT;
285             }
286           // Sharp
287           else if (text[last] == '#')
288             {
289               last++;
290               if (text[last] == '|') // Block comment
291                 {
292                   t.type = T_OPEN_COMMENT;
293                   endStateOfLastPara = S_COMMENT;
294                 }
295               // Boolean constant
296               else if (toupper(text[last]) == 'T' ||
297                        toupper(text[last]) == 'F')
298                 {
299                   t.type = T_CONSTANT;
300                 }
301               // Text constant
302               else if (text[last] == '!' ||
303                        text[last] == '\\' ||
304                        text[last] == '#')
305                 {
306                   last++;
307                   char c2 = getCharType(text[last]);
308                   while (last < length && c2 == C_LETTER) {
309                     last++;
310                     if (last < length)
311                       {
312                         c2 = getCharType(text[last]);
313                         if (c2 != C_LETTER)
314                           last--;
315                       }
316                   }
317                   t.type = T_CONSTANT;
318                 }
319               // Number
320               else if (toupper(text[last]) == 'E' ||
321                        toupper(text[last]) == 'I' ||
322                        toupper(text[last]) == 'B' ||
323                        toupper(text[last]) == 'D' ||
324                        toupper(text[last]) == 'O' ||
325                        toupper(text[last]) == 'X')
326                 {
327                   last = last;
328                   t.type = T_NUMBER;
329                   parseNumber(last, text);
330                 }
331               // Vector
332               else if (text[last] == '(') {
333                 last = last;
334                 t.type = T_CONSTANT;
335               }
336             }
337           // Identifier
338           else if (c == C_LETTER || c == C_INIT)
339             {
340               c = getCharType(text[last]);
341               while (last < length && (c == C_LETTER || c == C_NUMBER ||
342                                        c == C_INIT || c == C_NEXT))
343                 {
344                   last++;
345                   c = getCharType(text[last]);
346                 }
347               last--;
348 
349               if (searchKeyword(text.mid(current, last-current+1)))
350                 t.type = T_KEYWORD;
351               else t.type = T_IDENT;
352             }
353           else if (c == C_NEXT)
354             {
355               if (text[last] != '@')
356                 {
357                   if (last+1 == length || getCharType(text[last+1])==C_SPACE)
358                     t.type = T_IDENT;
359                   if (text[last] == '.' && length > last+2 &&
360                       text[last+1] == '.' && text[last+2] == '.')
361                     t.type = T_IDENT;
362                 }
363             }
364           // Number
365           else if (c == C_NUMBER)
366             {
367               parseNumber(last, text);
368               t.type = T_NUMBER;
369             }
370           // Other
371           else
372             {
373               if (text[last] == '=')
374                 t.type = T_IDENT;
375               if (text[last] == '\'')
376                 t.type = T_CONSTANT;
377             }
378         }
379 
380       if (t.type != T_NO_TOKEN)
381         {
382           // Add a token to the vector
383           t.start = current;
384           t.length = last-current+1;
385           tokens->append(t);
386         }
387 
388       current = last+1;
389     }
390 
391   return tokens;
392 }
393 
394 // DEBUG: print a vector of tokens
printTokens(QValueVector<TokenType> * tokens)395 void GuideUiHighlighterScheme::printTokens (QValueVector<TokenType>* tokens)
396 {
397   for (uint i=0; i<tokens->size(); i++)
398     {
399       TokenType t = (*tokens)[i];
400       switch(t.type)
401         {
402         case T_NO_TOKEN: printf("NOTOK"); break;
403         case T_IDENT: printf("IDENT"); break;
404         case T_KEYWORD: printf("KEYWO"); break;
405         case T_OPEN_PARENT: printf("OPENP"); break;
406         case T_CLOSE_PARENT: printf("CLOSP"); break;
407         case T_NUMBER: printf("NUMBE"); break;
408         case T_STRING: printf("STRIN"); break;
409         case T_CONSTANT: printf("CONST"); break;
410         case T_OPEN_COMMENT: printf("OPENC"); break;
411         case T_CLOSE_COMMENT: printf("CLOSC"); break;
412         case T_COMMENT: printf("COMME"); break;
413         default: printf("XXX%02d", t.type);
414         }
415       printf("%02d,%02d ",t.start,t.length);
416     }
417   printf("\n\n");
418 }
419 
420 // Parse number from current position
parseNumber(int & last,const QString & text)421 void GuideUiHighlighterScheme::parseNumber (int& last, const QString &text)
422 {
423   bool ok = true;
424   int length = text.length();
425 
426   while (last < length && ok) {
427     CharType c = getCharType(text[last]);
428     if (c != C_NUMBER && c != C_LETTER)
429       {
430         switch (text[last])
431           {
432           case '#': case '.': case '/': case '@': case '+': case '-': break;
433           default: ok = false; last--; return;
434           }
435       }
436     last++;
437   }
438   last--;
439 }
440 
441 // Returns the character type
getCharType(char c)442 GuideUiHighlighterScheme::CharType GuideUiHighlighterScheme::getCharType (char c)
443 {
444   if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')
445     return C_LETTER;
446   if (c >= '0' && c <= '9')
447     return C_NUMBER;
448 
449   switch (c)
450     {
451     case '!': case '$': case '%': case '&': case '*': case '/':
452     case ':': case '<': case '>': case '?': case '^': case '_':
453     case '~':
454       return C_INIT;
455     case '+': case '-': case '.': case '@':
456       return C_NEXT;
457     case ' ': case '\t': case '\n': case '\r':
458       return C_SPACE;
459     default:
460       return C_OTHER;
461     }
462 }
463 
464 // Search if text is a keyword
searchKeyword(QString text)465 bool GuideUiHighlighterScheme::searchKeyword (QString text)
466 {
467   for (int i=0; i<nbKeywords; i++)
468     {
469       if (text.compare(keywords[i]) == 0)
470         return true;
471     }
472   return false;
473 }
474 
475 // When cursor position changed, balance parentheses if the cursor is near of a parenthesis
cursorPositionChanged(int para,int col)476 void GuideUiHighlighterScheme::cursorPositionChanged (int para, int col)
477 {
478   highlightPara[0] = -1;
479   highlightColumn[0] = -1;
480   highlightPara[1] = -1;
481   highlightColumn[1] = -1;
482 
483   if (!schemeCodeFormat->matchParent)
484     {
485       rehighlight();
486       return;
487     }
488 
489   int tokenIndex[2];
490   QString text = textEdit()->text(para);
491   int endState = getEndState(para-1);
492   QValueVector<TokenType> *tokens = getTokens(text, endState);
493 
494   highlightPara[0] = highlightPara[1] = -1;
495 
496   // Search for parenthesis near the cursor position
497   for (uint i=0; i<tokens->size(); i++)
498     {
499       TokenType t = (*tokens)[i];
500       if (t.type == T_OPEN_PARENT &&
501           highlightPara[0] == -1 &&
502           t.start <= col && t.start >= col-1)
503         {
504           highlightPara[0] = para;
505           highlightColumn[0] = t.start;
506           tokenIndex[0] = i;
507         }
508       if (t.type == T_CLOSE_PARENT &&
509           highlightPara[1] == -1 &&
510           t.start <= col && t.start >= col-1)
511         {
512           highlightPara[1] = para;
513           highlightColumn[1] = t.start;
514           tokenIndex[1] = i;
515         }
516     }
517 
518   if (highlightPara[0] < 0 && highlightPara[1] < 0)
519     {
520       rehighlight();
521       return;
522     }
523 
524   int paraNb = para, tokenNb;
525   if (highlightPara[1] >= 0)
526     tokenNb = tokenIndex[1];
527   else
528     tokenNb = tokenIndex[0];
529 
530   int colNb = searchOtherParenthesis(paraNb, tokenNb);
531 
532   if (colNb >= 0)
533     {
534       if (highlightPara[1] >= 0)
535         {
536           highlightPara[0] = paraNb;
537           highlightColumn[0] = colNb;
538         }
539       else
540         {
541           highlightPara[1] = paraNb;
542           highlightColumn[1] = colNb;
543         }
544     }
545 
546   rehighlight();
547 }
548 
549 // Get the indent length of the next paragraph
getIndentLength(int para)550 int GuideUiHighlighterScheme::getIndentLength (int para)
551 {
552   if (!schemeCodeFormat->autoIndent) return -1;
553   QValueVector<TokenType> *tokens;
554 
555   // Search for the open parenthesis which belongs to this line
556   int row = para-1;
557   QString text = textEdit()->text(row);
558   int endState = getEndState(row-1);
559   tokens = getTokens(text, endState);
560   int tokenNb = tokens->size()-1;
561   int level = 0; // Parenthesis level
562   while (level >= 0 && row >= 0)
563     {
564       // Check the current token
565       if (tokenNb >= 0)
566         {
567           TokenType t = (*tokens)[tokenNb];
568           if (t.type == T_OPEN_PARENT)
569             level--;
570           else if (t.type == T_CLOSE_PARENT)
571             level++;
572         }
573       if (level < 0) continue;
574 
575       // Go to next token
576       tokenNb--;
577       if (tokenNb < 0)
578         {
579           delete tokens;
580           row--;
581           text = textEdit()->text(row);
582           endState = getEndState(row-1);
583           tokens = getTokens(text, endState);
584           tokenNb = tokens->size()-1;
585         }
586     }
587 
588   if (level >= 0)
589     {
590       delete tokens;
591       return -1;
592     }
593 
594   // Calculates the indent length
595   int indent;
596   switch (tokens->size()-tokenNb-1)
597     {
598     case 0: indent = (*tokens)[tokenNb].start+1; break;
599     case 1: indent = (*tokens)[tokenNb].start+2; break;
600     default:
601       if ((*tokens)[tokenNb+1].type == T_KEYWORD)
602         indent = (*tokens)[tokenNb].start+2;
603       else
604         indent = (*tokens)[tokenNb+2].start;
605       break;
606     }
607   delete tokens;
608   return indent;
609 }
610 
611 // When the user double-clicks the textEdit, selects the pointed expression
doubleClicked(int para,int index)612 void GuideUiHighlighterScheme::doubleClicked (int para, int index)
613 {
614   int paraStart = para, paraEnd = para,
615     colStart = index, colEnd = index + 1;
616 
617   QString text = textEdit()->text(para);
618   int endState = getEndState(para-1);
619   QValueVector<TokenType> *tokens = getTokens(text, endState);
620   TokenType t;
621 
622   // Search the double-clicked token
623   for (uint i=0; i<tokens->size(); i++)
624     {
625       t = (*tokens)[i];
626       if (index < t.start || index >= t.start + t.length)
627         continue;
628 
629       // If it is a parenthesis, selects the entire expression
630       if (t.type == T_OPEN_PARENT ||
631           t.type == T_CLOSE_PARENT)
632         {
633           int paraNb = para;
634           int tokenNb = i;
635           int colNb = searchOtherParenthesis(paraNb, tokenNb);
636           if (t.type == T_OPEN_PARENT)
637             {
638               paraStart = para;
639               colStart = t.start;
640               paraEnd = paraNb;
641               colEnd = colNb + 1;
642             }
643           else
644             {
645               paraStart = paraNb;
646               colStart = colNb;
647               paraEnd = para;
648               colEnd = t.start + 1;
649             }
650         }
651       else
652         {
653           paraStart = paraEnd = para;
654           colStart = t.start;
655           colEnd = t.start + t.length;
656         }
657       break;
658     }
659 
660   textEdit()->setSelection(paraStart, colStart, paraEnd, colEnd);
661 }
662 
663 // Search for the related parenthesis of the pointed parenthesis
searchOtherParenthesis(int & paraNb,int & tokenNb)664 int GuideUiHighlighterScheme::searchOtherParenthesis (int &paraNb, int &tokenNb)
665 {
666   int level = 0;
667   QString text = textEdit()->text(paraNb);
668   int endState = getEndState(paraNb-1);
669   QValueVector<TokenType> *tokens = getTokens(text, endState);
670   if (tokenNb > (int)tokens->size()) {
671     delete tokens;
672     return -1;
673   }
674   TokenType t = (*tokens)[tokenNb];
675   int parentType = t.type;
676   // Ensure this is a parenthesis
677   if (parentType != T_OPEN_PARENT &&
678       parentType != T_CLOSE_PARENT)
679     {
680       delete tokens;
681       return -1;
682     }
683 
684   // Search for the corresponding open parenthesis
685   if (parentType == T_CLOSE_PARENT)
686     {
687       do
688         {
689           // Check the current token
690           if (tokenNb >= 0)
691             {
692               t = (*tokens)[tokenNb];
693               if (t.type == T_OPEN_PARENT)
694                 level--;
695               if (t.type == T_CLOSE_PARENT)
696                 level++;
697             }
698           if (level == 0) continue;
699 
700           // Go to the next token
701           tokenNb--;
702           if (tokenNb < 0)
703             {
704               paraNb--;
705               if (paraNb < 0)
706                 {
707                   if (level > 0) level = -1;
708                   tokenNb = 0;
709                 }
710               else
711                 {
712                   delete tokens;
713                   text = textEdit()->text(paraNb);
714                   endState = getEndState(paraNb-1);
715                   tokens = getTokens(text, endState);
716                   tokenNb = tokens->size()-1;
717                 }
718             }
719         } while (level > 0);
720 
721       // If the parenthesis is not found
722       if (level != 0)
723         paraNb = -1;
724     }
725 
726   // Search for the corresponding close parenthesis
727   else if (highlightPara[0] >= 0)
728     {
729       do
730         {
731           // Check the current token
732           if (tokenNb < (int)tokens->size())
733             {
734               t = (*tokens)[tokenNb];
735               if (t.type == T_OPEN_PARENT)
736                 level++;
737               if (t.type == T_CLOSE_PARENT)
738                 level--;
739             }
740           if (level == 0) continue;
741 
742           // Go to the next token
743           tokenNb++;
744           if (tokenNb >= (int)tokens->size())
745             {
746               paraNb++;
747               tokenNb = 0;
748               if (paraNb >= textEdit()->paragraphs())
749                 {
750                   if (level > 0) level = -1;
751                 }
752               else
753                 {
754                   delete tokens;
755                   text = textEdit()->text(paraNb);
756                   endState = getEndState(paraNb-1);
757                   tokens = getTokens(text, endState);
758                 }
759             }
760         } while (level > 0);
761 
762       // If the parenthesis is not found
763       if (level != 0)
764         paraNb = -1;
765     }
766 
767   delete tokens;
768 
769   if (paraNb == -1) return -1;
770   return t.start;
771 }
772 
773 // Sets the end state of the current paragraph
setEndState(int para,int endState)774 void GuideUiHighlighterScheme::setEndState (int para, int endState)
775 {
776   if (para < 0) return;
777   if (para >= (int)endStates.size())
778     endStates.resize(para+1, 0);
779   endStates[para] = endState;
780 }
781 
782 // Returns the end state of the paragraph
getEndState(int para)783 int GuideUiHighlighterScheme::getEndState (int para)
784 {
785   if (para < 0)
786     return -2;
787   if (para >= (int)endStates.size())
788     return 0;
789   return endStates[para];
790 }
791 
792 /*---------------------------------------------------------------------------*/
793 
794 /* Local Variables: */
795 /* mode: C++ */
796 /* End: */
797