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