1 //===========================================
2 //  Lumina-DE source code
3 //  Copyright (c) 2015, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #ifndef _LUMINA_SYNTAX_HIGHLIGHER_CPP_H
8 #define _LUMINA_SYNTAX_HIGHLIGHER_CPP_H
9 
10 #include <QSyntaxHighlighter>
11 #include <QTextDocument>
12 #include <QTextCharFormat>
13 #include <QString>
14 #include <QSettings>
15 #include <QDebug>
16 #include <QDateTime>
17 #include <QJsonObject>
18 #include <QPlainTextEdit>
19 
20 
21 //Simple syntax rules
22 struct SyntaxRule{
23   QRegExp pattern;  //single-line rule
24   QRegExp startPattern, endPattern;  //multi-line rules
25   QTextCharFormat format;
26 };
27 
28 class SyntaxFile{
29 private:
30   QJsonObject metaObj;
31   QJsonObject formatObj;
32 
33   QColor colorFromOption(QString opt, QSettings *settings);
34 
35 public:
36   QVector<SyntaxRule> rules;
37   QDateTime lastLoaded;
38   QString fileLoaded;
39 
SyntaxFile()40   SyntaxFile(){}
41 
42   QString name();
43   int char_limit();
44   bool highlight_excess_whitespace();
45   bool check_spelling();
46   int tab_length();
47 
48   void SetupDocument(QPlainTextEdit *editor);
49   bool supportsFile(QString file); //does this syntax set support the file?
50   bool supportsFirstLine(QString line); //is the type of file defined by the first line of the file? ("#!/bin/<something>" for instance)
51 
52   //Main Loading routine (run this before other functions)
53   bool LoadFile(QString file, QSettings *settings);
54 
55   //Main function for finding/loading all syntax files
56   static QList<SyntaxFile> availableFiles(QSettings *settings);
57 };
58 
59 class Custom_Syntax : public QSyntaxHighlighter{
60   Q_OBJECT
61 private:
62   QSettings *settings;
63         SyntaxFile syntax;
64 
65 public:
QSyntaxHighlighter(parent)66   Custom_Syntax(QSettings *set, QTextDocument *parent = 0) : QSyntaxHighlighter(parent){
67     settings = set;
68   }
~Custom_Syntax()69   ~Custom_Syntax(){}
70 
loadedRules()71   QString loadedRules(){ return syntax.name(); }
72 
73   static QStringList availableRules(QSettings *settings);
74   static QStringList knownColors();
75   static void SetupDefaultColors(QSettings *settings);
76   static QString ruleForFile(QString filename, QSettings *settings);
77   static QString ruleForFirstLine(QString line, QSettings *settings);
78   void loadRules(QString type);
79   void loadRules(SyntaxFile sfile);
80 
reloadRules()81   void reloadRules(){
82     loadRules( syntax.name() );
83   }
84 
setupDocument(QPlainTextEdit * edit)85   void setupDocument(QPlainTextEdit *edit){ syntax.SetupDocument(edit); } //simple redirect for the function in the currently-loaded rules
86 
87 protected:
highlightBlock(const QString & text)88   void highlightBlock(const QString &text){
89           //qDebug() << "Highlight Block:" << text;
90     //Now look for any multi-line patterns (starting/continuing/ending)
91     int start = 0;
92     int splitactive = previousBlockState();
93     if(splitactive>syntax.rules.length()-1){ splitactive = -1; } //just in case
94 
95     while(start>=0 && start<=text.length()-1){
96       //qDebug() << "split check:" << start << splitactive;
97       if(splitactive>=0){
98         //Find the end of the current rule
99         int end = syntax.rules[splitactive].endPattern.indexIn(text, start);
100         if(end==-1){
101                 //qDebug() << "Highlight to end of line:" << text << start;
102           //rule did not finish - apply to all
103                 if(start>0){ setFormat(start-1, text.length()-start+1, syntax.rules[splitactive].format); }
104                 else{ setFormat(start, text.length()-start, syntax.rules[splitactive].format); }
105     break; //stop looking for more multi-line patterns
106         }else{
107     //Found end point within the same line
108                 //qDebug() << "Highlight to particular point:" << text << start << end;
109     int len = end-start+syntax.rules[splitactive].endPattern.matchedLength();
110                 if(start>0){ start--; len++; } //need to include the first character as well
111     setFormat(start, len , syntax.rules[splitactive].format);
112     start+=len; //move pointer to the end of handled range
113     splitactive = -1; //done with this rule
114         }
115       } //end check for end match
116       //Look for the start of any new split rules
117       //qDebug() << "Loop over multi-line rules";
118       for(int i=0; i<syntax.rules.length() && splitactive<0; i++){
119         //qDebug() << "Check Rule:" << i << syntax.rules[i].startPattern << syntax.rules[i].endPattern;
120               if(syntax.rules[i].startPattern.isEmpty()){ continue; }
121               //qDebug() << "Look for start of split rule:" << syntax.rules[i].startPattern << splitactive;
122         int newstart = syntax.rules[i].startPattern.indexIn(text,start);
123         if(newstart>=start){
124                 //qDebug() << "Got Start of split rule:" << start << newstart << text;
125     splitactive = i;
126     start = newstart+1;
127                 if(start>=text.length()-1){
128                   //qDebug() << "Special case: start now greater than line length";
129                   //Need to apply highlighting to this section too - start matches the end of the line
130                   setFormat(start-1, text.length()-start+1, syntax.rules[splitactive].format);
131                 }
132         }
133       }
134       if(splitactive<0){  break; } //no other rules found - go ahead and exit the loop
135           } //end scan over line length and multi-line formats
136 
137     setCurrentBlockState(splitactive);
138           //Do all the single-line patterns
139     for(int i=0; i<syntax.rules.length(); i++){
140             if(syntax.rules[i].pattern.isEmpty()){ continue; } //not a single-line rule
141       QRegExp patt(syntax.rules[i].pattern); //need a copy of the rule's pattern (will be changing it below)
142       int index = patt.indexIn(text);
143             if(splitactive>=0 || index<start){ continue; } //skip this one - falls within a multi-line pattern above
144       while(index>=0){
145         int len = patt.matchedLength();
146         if(format(index)==currentBlock().charFormat()){ setFormat(index, len, syntax.rules[i].format); } //only apply highlighting if not within a section already
147         index = patt.indexIn(text, index+len); //go to the next match
148       }
149     }//end loop over normal (single-line) patterns
150 
151     //Now go through and apply any document-wide formatting rules
152           QTextCharFormat fmt;
153           fmt.setBackground( QColor( settings->value("colors/bracket-missing").toString() ) );
154           int max = syntax.char_limit();
155     if(max >= 0 && ( (text.length()+(text.count("\t")*(syntax.tab_length()-1)) )> max) ) {
156       //Line longer than it should be - highlight the offending characters
157       //Need to be careful about where tabs show up in the line
158       int len = 0;
159       for(int i=0; i<text.length() and len<=max; i++){
160         len += (text[i]=='\t') ? syntax.tab_length() : 1;
161         if(len>max)
162           setFormat(i, text.length()-i, fmt);
163       }
164     }
165     if(syntax.highlight_excess_whitespace()){
166       int last = text.length()-1;
167       while(last>=0 && (text[last]==' ' || text[last]=='\t' ) ){ last--; }
168       if(last < text.length()-1){
169         setFormat(last+1, text.length()-1-last, fmt);
170       }
171     }
172   }
173 };
174 #endif
175