MatchingText.cpp 8.54 KB
Newer Older
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
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "MatchingText.h"
#include "BackwardsScanner.h"

#include <Token.h>
33
34
35

#include <QtGui/QTextDocument>
#include <QtCore/QtDebug>
36
37
38

using namespace CPlusPlus;

39
enum { MAX_NUM_LINES = 20 };
40

41
static bool shouldOverrideChar(const QChar &ch)
42
{
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
43
44
45
46
47
48
49
    switch (ch.unicode()) {
    case ')': case ']': case ';': case '"': case '\'':
        return true;

    default:
        return false;
    }
50
51
}

52
static bool isCompleteStringLiteral(const BackwardsScanner &tk, int index)
53
{
54
    const QStringRef text = tk.textRef(index);
55
56
57
58
59
60
61
62
63
64

    if (text.length() < 2)
        return false;

    else if (text.at(text.length() - 1) == QLatin1Char('"'))
        return text.at(text.length() - 2) != QLatin1Char('\\'); // ### not exactly.

    return false;
}

65
static bool isCompleteCharLiteral(const BackwardsScanner &tk, int index)
66
{
67
    const QStringRef text = tk.textRef(index);
68
69
70
71
72
73
74
75
76
77

    if (text.length() < 2)
        return false;

    else if (text.at(text.length() - 1) == QLatin1Char('\''))
        return text.at(text.length() - 2) != QLatin1Char('\\'); // ### not exactly.

    return false;
}

78
79
80
81
82
83
84
85
86
87
MatchingText::MatchingText()
{ }

bool MatchingText::shouldInsertMatchingText(const QTextCursor &tc)
{
    QTextDocument *doc = tc.document();
    return shouldInsertMatchingText(doc->characterAt(tc.selectionEnd()));
}

bool MatchingText::shouldInsertMatchingText(const QChar &lookAhead)
88
{
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
89
90
91
92
    switch (lookAhead.unicode()) {
    case '{': case '}':
    case ']': case ')':
    case ';': case ',':
93
94
        return true;

Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
95
96
97
98
99
100
    default:
        if (lookAhead.isSpace())
            return true;

        return false;
    } // switch
101
102
103
104
}

QString MatchingText::insertMatchingBrace(const QTextCursor &cursor, const QString &textToProcess,
                                          const QChar &la, int *skippedChars) const
105
106
107
108
109
110
111
112
113
114
115
116
117
{
    QTextCursor tc = cursor;
    QString text = textToProcess;

    const QString blockText = tc.block().text().mid(tc.columnNumber());
    const int length = qMin(blockText.length(), textToProcess.length());

    for (int i = 0; i < length; ++i) {
        const QChar ch1 = blockText.at(i);
        const QChar ch2 = textToProcess.at(i);

        if (ch1 != ch2)
            break;
118
        else if (! shouldOverrideChar(ch1))
119
120
121
122
123
124
125
126
127
128
            break;

        ++*skippedChars;
    }

    if (*skippedChars != 0) {
        tc.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, *skippedChars);
        text = textToProcess.mid(*skippedChars);
    }

129
    if (text.isEmpty() || !shouldInsertMatchingText(la))
130
131
132
133
134
135
136
137
138
139
140
141
        return QString();

    BackwardsScanner tk(tc, textToProcess.left(*skippedChars), MAX_NUM_LINES);
    const int startToken = tk.startToken();
    int index = startToken;

    const SimpleToken &token = tk[index - 1];

    if (text.at(0) == QLatin1Char('"') && (token.is(T_STRING_LITERAL) || token.is(T_WIDE_STRING_LITERAL))) {
        if (text.length() != 1)
            qWarning() << Q_FUNC_INFO << "handle event compression";

142
        if (isCompleteStringLiteral(tk, index - 1))
143
144
145
146
147
148
149
            return QLatin1String("\"");

        return QString();
    } else if (text.at(0) == QLatin1Char('\'') && (token.is(T_CHAR_LITERAL) || token.is(T_WIDE_CHAR_LITERAL))) {
        if (text.length() != 1)
            qWarning() << Q_FUNC_INFO << "handle event compression";

150
        if (isCompleteCharLiteral(tk, index - 1))
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
            return QLatin1String("'");

        return QString();
    }

    QString result;

    foreach (const QChar &ch, text) {
        if      (ch == QLatin1Char('('))  result += ')';
        else if (ch == QLatin1Char('['))  result += ']';
        else if (ch == QLatin1Char('"'))  result += '"';
        else if (ch == QLatin1Char('\'')) result += '\'';
    }

    return result;
}

168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
bool MatchingText::shouldInsertNewline(const QTextCursor &tc) const
{
    QTextDocument *doc = tc.document();
    int pos = tc.selectionEnd();

    // count the number of empty lines.
    int newlines = 0;
    for (int e = doc->characterCount(); pos != e; ++pos) {
        const QChar ch = doc->characterAt(pos);

        if (! ch.isSpace())
            break;
        else if (ch == QChar::ParagraphSeparator)
            ++newlines;
    }

    if (newlines <= 1 && doc->characterAt(pos) != QLatin1Char('}'))
        return true;

    return false;
}

190
191
QString MatchingText::insertParagraphSeparator(const QTextCursor &tc) const
{
192
    BackwardsScanner tk(tc, QString(), MAX_NUM_LINES);
193
194
195
196
197
    int index = tk.startToken();

    if (tk[index - 1].isNot(T_LBRACE))
        return QString(); // nothing to do.

198
199
200
201
    const QString textBlock = tc.block().text().mid(tc.columnNumber()).trimmed();
    if (! textBlock.isEmpty())
        return QString();

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
    --index; // consume the `{'

    const SimpleToken &token = tk[index - 1];

    if (token.is(T_STRING_LITERAL) && tk[index - 2].is(T_EXTERN)) {
        // recognized extern "C"
        return QLatin1String("}");

    } else if (token.is(T_IDENTIFIER)) {
        int i = index - 1;

        forever {
            const SimpleToken &current = tk[i - 1];

            if (current.is(T_EOF_SYMBOL))
                break;

219
220
221
222
223
224
225
226
227
            else if (current.is(T_CLASS) || current.is(T_STRUCT) || current.is(T_UNION)) {
                // found a class key.
                QString str = QLatin1String("};");

                if (shouldInsertNewline(tc))
                    str += QLatin1Char('\n');

                return str;
            }
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286

            else if (current.is(T_NAMESPACE))
                return QLatin1String("}"); // found a namespace declaration

            else if (current.is(T_SEMICOLON))
                break; // found the `;' sync token

            else if (current.is(T_LBRACE) || current.is(T_RBRACE))
                break; // braces are considered sync tokens

            else if (current.is(T_LPAREN) || current.is(T_RPAREN))
                break; // sync token

            else if (current.is(T_LBRACKET) || current.is(T_RBRACKET))
                break; // sync token

            --i;
        }
    }

    if (token.is(T_NAMESPACE)) {
        // anonymous namespace
        return QLatin1String("}");

    } else if (token.is(T_CLASS) || token.is(T_STRUCT) || token.is(T_UNION)) {
        if (tk[index - 2].is(T_TYPEDEF)) {
            // recognized:
            //   typedef struct {
            //
            // in this case we don't want to insert the extra semicolon+newline.
            return QLatin1String("}");
        }

        // anonymous class
        return QLatin1String("};");

    } else if (token.is(T_RPAREN)) {
        // search the matching brace.
        const int lparenIndex = tk.startOfMatchingBrace(index);

        if (lparenIndex == index) {
            // found an unmatched brace. We don't really know to do in this case.
            return QString();
        }

        // look at the token before the matched brace
        const SimpleToken &tokenBeforeBrace = tk[lparenIndex - 1];

        if (tokenBeforeBrace.is(T_IF)) {
            // recognized an if statement
            return QLatin1String("}");

        } else if (tokenBeforeBrace.is(T_FOR) || tokenBeforeBrace.is(T_WHILE)) {
            // recognized a for-like statement
            return QLatin1String("}");

        }

        // if we reached this point there is a good chance that we are parsing a function definition
287
288
289
290
291
292
        QString str = QLatin1String("}");

        if (shouldInsertNewline(tc))
            str += QLatin1Char('\n');

        return str;
293
294
295
296
297
    }

    // match the block
    return QLatin1String("}");
}