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 ¶Nb, 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