aboutsummaryrefslogtreecommitdiff
path: root/src-qt5/desktop-utils/lumina-textedit/syntaxSupport.h
blob: 4ce2424cda1b3ea1bc779c8a959a578024771a12 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//===========================================
//  Lumina-DE source code
//  Copyright (c) 2015, Ken Moore
//  Available under the 3-clause BSD license
//  See the LICENSE file for full details
//===========================================
#ifndef _LUMINA_SYNTAX_HIGHLIGHER_CPP_H
#define _LUMINA_SYNTAX_HIGHLIGHER_CPP_H

#include <QSyntaxHighlighter>
#include <QTextDocument>
#include <QTextCharFormat>
#include <QString>
#include <QSettings>
#include <QDebug>
#include <QDateTime>
#include <QJsonObject>
#include <QPlainTextEdit>


//Simple syntax rules
struct SyntaxRule{
  QRegExp pattern;  //single-line rule
  QRegExp startPattern, endPattern;  //multi-line rules
  QTextCharFormat format;
};

class SyntaxFile{
private:
  QJsonObject metaObj;
  QJsonObject formatObj;

  QColor colorFromOption(QString opt, QSettings *settings);

public:
  QVector<SyntaxRule> rules;
  QDateTime lastLoaded;
  QString fileLoaded;

  SyntaxFile(){}

  QString name();
  int char_limit();
  bool highlight_excess_whitespace();
  bool check_spelling();
  int tab_length();

  void SetupDocument(QPlainTextEdit *editor);
  bool supportsFile(QString file); //does this syntax set support the file?
  bool supportsFirstLine(QString line); //is the type of file defined by the first line of the file? ("#!/bin/<something>" for instance)

  //Main Loading routine (run this before other functions)
  bool LoadFile(QString file, QSettings *settings);

  //Main function for finding/loading all syntax files
  static QList<SyntaxFile> availableFiles(QSettings *settings);
};

class Custom_Syntax : public QSyntaxHighlighter{
  Q_OBJECT
private:
  QSettings *settings;
        SyntaxFile syntax;

public:
  Custom_Syntax(QSettings *set, QTextDocument *parent = 0) : QSyntaxHighlighter(parent){
    settings = set;
  }
  ~Custom_Syntax(){}

  QString loadedRules(){ return syntax.name(); }

  static QStringList availableRules(QSettings *settings);
  static QStringList knownColors();
  static void SetupDefaultColors(QSettings *settings);
  static QString ruleForFile(QString filename, QSettings *settings);
  static QString ruleForFirstLine(QString line, QSettings *settings);
  void loadRules(QString type);
  void loadRules(SyntaxFile sfile);

  void reloadRules(){
    loadRules( syntax.name() );
  }

  void setupDocument(QPlainTextEdit *edit){ syntax.SetupDocument(edit); } //simple redirect for the function in the currently-loaded rules

protected:

  void highlightBlock(const QString &text){
    QTextCharFormat defaultFormat;
          //qDebug() << "Highlight Block:" << text;
    //Now  finish any multi-line patterns
    int start = 0;
    int splitactive = previousBlockState();
    if(splitactive>syntax.rules.length()-1){ splitactive = -1; } //just in case

    //qDebug() << "split check:" << start << splitactive;
    if(splitactive>=0){
      //Find the end of the current rule
      int end = syntax.rules[splitactive].endPattern.indexIn(text, start);
      if(end==-1){
        //qDebug() << "Highlight to end of line:" << text << start;
        //rule did not finish - apply to all
        if(start>0){ setFormat(start-1, text.length()-start+1, syntax.rules[splitactive].format); }
        else{ setFormat(start, text.length()-start, syntax.rules[splitactive].format); }
      }else{
        //Found end point within the same line
        //qDebug() << "Highlight to particular point:" << text << start << end;
        int len = end-start+syntax.rules[splitactive].endPattern.matchedLength();
        if(start>0){ start--; len++; } //need to include the first character as well
        setFormat(start, len , syntax.rules[splitactive].format);
        start+=len; //move pointer to the end of handled range
        splitactive = -1; //done with this rule
      }
    } //end check for end of pre-existing multi-line block match
    setCurrentBlockState(splitactive); //tag this block as continuing as well

    //Do all the single-line patterns
    for(int i=0; i<syntax.rules.length() && splitactive<0; i++){
      if(syntax.rules[i].pattern.isEmpty()){ continue; } //not a single-line rule
      QRegExp patt(syntax.rules[i].pattern); //need a copy of the rule's pattern (will be changing it below)
      int index = patt.indexIn(text);
      if( index<start ){ continue; } //skip this one - falls within a multi-line pattern above
      while(index>=0){
        int len = patt.matchedLength();
        if(format(index)==currentBlock().charFormat()){ setFormat(index, len, syntax.rules[i].format); } //only apply highlighting if not within a section already
        index = patt.indexIn(text, index+len); //go to the next match
      }
    }//end loop over normal (single-line) patterns

    //Look for the start of any new split rules
    //qDebug() << "Loop over multi-line rules";
    for(int i=0; i<syntax.rules.length() && splitactive<0; i++){
      //qDebug() << "Check Rule:" << i << syntax.rules[i].startPattern << syntax.rules[i].endPattern;
      if(syntax.rules[i].startPattern.isEmpty()){ continue; }
      //qDebug() << "Look for start of split rule:" << syntax.rules[i].startPattern << splitactive;
      int newstart = syntax.rules[i].startPattern.indexIn(text,start);
      if(newstart>=start && (format(newstart) == defaultFormat) ){ //only start multi-line formatting if it is not already contained in a single-line formatting
        //qDebug() << "Got Start of split rule:" << start << newstart << text << (format(newstart) != defaultFormat);
        splitactive = i;
        start = newstart+1;
        int end = syntax.rules[splitactive].endPattern.indexIn(text, start);
        if(end>0){ //end of multi-line comment in the same block
          setFormat(start-1, end-start+1, syntax.rules[splitactive].format);
          start = end+1;
        }else{
          setCurrentBlockState(splitactive);
        }
        if(start>=text.length()-1 || splitactive>=0){
          //qDebug() << "Special case: start now greater than line length";
          //Need to apply highlighting to this section too - start matches the end of the line
          setFormat(start-1, text.length()-start+1, syntax.rules[splitactive].format);
          break; //this goes to the end of the text block
        }
      }
    }

    //Now go through and apply any document-wide formatting rules
    QTextCharFormat fmt;
    fmt.setBackground( QColor( settings->value("colors/bracket-missing").toString() ) );
    int max = syntax.char_limit();
    if(max >= 0 && ( (text.length()+(text.count("\t")*(syntax.tab_length()-1)) )> max) ) {
      //Line longer than it should be - highlight the offending characters
      //Need to be careful about where tabs show up in the line
      int len = 0;
      for(int i=0; i<text.length() and len<=max; i++){
        len += (text[i]=='\t') ? syntax.tab_length() : 1;
        if(len>max)
          setFormat(i, text.length()-i, fmt);
      }
    }
    if(syntax.highlight_excess_whitespace()){
      int last = text.length()-1;
      while(last>=0 && (text[last]==' ' || text[last]=='\t' ) ){ last--; }
      if(last < text.length()-1){
        setFormat(last+1, text.length()-1-last, fmt);
      }
    }
  }
};
#endif
bgstack15