aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio/sqlitesyntaxhighlighter.h
blob: c11ab7d97cb6f7ae3644d8a96648f31e4cc38bd9 (plain) (blame)
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
182
183
184
185
186
187
188
189
190
#ifndef SQLITESYNTAXHIGHLIGHTER_H
#define SQLITESYNTAXHIGHLIGHTER_H

#include "parser/token.h"
#include "syntaxhighlighterplugin.h"
#include "plugins/builtinplugin.h"
#include "guiSQLiteStudio_global.h"
#include <QSyntaxHighlighter>
#include <QRegularExpression>

class QWidget;

class GUI_API_EXPORT TextBlockData : public QTextBlockUserData
{
    public:
        struct GUI_API_EXPORT Parenthesis
        {
            char character;
            int position;
            int operator==(const Parenthesis& other);
        };

        QList<const Parenthesis*> parentheses();
        void insertParenthesis(int pos, char c);
        const Parenthesis* parenthesisForPosision(int pos);

        bool getEndsWithError() const;
        void setEndsWithError(bool value);

        bool getEndsWithQuerySeparator() const;
        void setEndsWithQuerySeparator(bool value);

    private:
        QList<Parenthesis> parData;
        bool endsWithError = false;
        bool endsWithQuerySeparator = false;
};

class GUI_API_EXPORT SqliteSyntaxHighlighter : public QSyntaxHighlighter
{
        Q_OBJECT
    public:
        enum class State
        {
            STANDARD,
            PARENTHESIS,
            STRING,
            KEYWORD,
            BIND_PARAM,
            BLOB,
            COMMENT,
            NUMBER
        };

        SqliteSyntaxHighlighter(QTextDocument *parent, const QHash<State,QTextCharFormat>* formats);
        explicit SqliteSyntaxHighlighter(QTextDocument *parent);

        void addError(int from, int to, bool limitedDamage = false);
        void clearErrors();
        bool haveErrors();

        void addDbObject(int from, int to);
        void clearDbObjects();

        bool getObjectLinksEnabled() const;
        void setObjectLinksEnabled(bool value);

        void addCustomBgColor(int from, int to, const QColor& color);
        void clearCustomBgColors();

        bool getCreateTriggerContext() const;
        void setCreateTriggerContext(bool value);

        static constexpr int MAX_QUERY_LENGTH = 100000;

    protected:
        void highlightBlock(const QString &text);

    private:
        enum class TextBlockState
        {
            REGULAR = -1, // default, the -1 is default of QSyntaxHighlighter
            BLOB,         // x'blob'
            STRING,       // 'string'
            COMMENT,      // /* comment */
            ID_1,         // [id]
            ID_2,         // "id"
            ID_3          // `id`
        };

        struct Error
        {
            Error(int from, int to, bool limitedDamage = false);

            int from;
            int to;
            bool limitedDamage = false; // if it's just an invalid token, but parser dealt with it, mark only this token
        };

        struct DbObject
        {
            DbObject(int from, int to);

            int from;
            int to;
        };

        void init(const QHash<State,QTextCharFormat>* formats);

        void setupMapping();

        /**
         * @brief getPreviousStatePrefix Provides prefix for previous block's state.
         * @param textBlockState Previous text block's state.
         * @return Prefix string (if any) for lexer to provide proper tokens according to previous state.
         */
        QString getPreviousStatePrefix(TextBlockState textBlockState);

        /**
         * @brief handleToken Highlights token.
         * @param token Token to handle.
         * @param idxModifier Modifier for text highlighting in case of previous state defined by multi-character token. See getPreviousStatePrefix() for details.
         * @return true if the token is being marked as invalid (syntax error).
         */
        bool handleToken(TokenPtr token, TokenPtr aheadToken, qint32 idxModifier, int errorStart, TextBlockData* currBlockData, TextBlockData* previousBlockData);

        bool isError(int start, int lgt, bool* limitedDamage);
        bool isValid(int start, int lgt);

        /**
         * @brief markUncheckedErrors Marks text as being uncheck for possible errors.
         * @param errorStart Start index of unchecked text.
         * Unchecked text is all text after first error, becuase it could not be parser, therefore could not be checked.
         */
        void markUncheckedErrors(int errorStart, int length);
        void setStateForUnfinishedToken(TolerantTokenPtr tolerantToken);

        /**
         * @brief applyErrorFormat Applies error format properties to given format.
         * @param format Format to apply properties to.
         * @param isError true if error was detected and error format needs to be applied.
         * @param wasError true if there was an error already in previous token.
         */
        void applyErrorFormat(QTextCharFormat& format, bool isError, bool wasError, Token::Type tokenType);

        /**
         * @brief applyValidObjectFormat Applies valid database object format properties to given format.
         * @param format Format to apply properties to.
         * @param isValid true if we're about to mark valid database object
         * @param qtextdisError true if error was detected and error format needs to be applied.
         * @param wasError true if there was an error already in previous token.
         */
        void applyValidObjectFormat(QTextCharFormat& format, bool isValid, bool isError, bool wasError);

        void handleParenthesis(TokenPtr token, TextBlockData* data);

        static const int regulartTextBlockState = static_cast<int>(TextBlockState::REGULAR);

        QHash<Token::Type,State> tokenTypeMapping;
        QList<Error> errors;
        QList<DbObject> dbObjects;
        bool objectLinksEnabled = false;
        bool createTriggerContext = false;
        const QHash<State,QTextCharFormat>* formats = nullptr;
};

class GUI_API_EXPORT SqliteHighlighterPlugin : public BuiltInPlugin, public SyntaxHighlighterPlugin
{
    Q_OBJECT

    SQLITESTUDIO_PLUGIN_TITLE("SQL highlighter")
    SQLITESTUDIO_PLUGIN_DESC("SQL (SQLite) syntax highlighter.")
    SQLITESTUDIO_PLUGIN_VERSION(10100)
    SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl")

    public:
        bool init();
        QString getLanguageName() const;
        QSyntaxHighlighter* createSyntaxHighlighter(QWidget* textEdit) const;
        void refreshFormats();
        QString previewSampleCode() const;
        const QHash<SqliteSyntaxHighlighter::State,QTextCharFormat>* getFormats() const;

    private:
        QHash<SqliteSyntaxHighlighter::State,QTextCharFormat> formats;
};

GUI_API_EXPORT int qHash(SqliteSyntaxHighlighter::State state);

#endif // SQLITESYNTAXHIGHLIGHTER_H