OSDN Git Service

6bef9dfb1ce083b071ac40665df72773a226bba6
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmljseditor / qmljsautocompleter.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "qmljsautocompleter.h"
35
36 #include <qmljs/qmljsscanner.h>
37
38 #include <QtCore/QChar>
39 #include <QtCore/QLatin1Char>
40 #include <QtGui/QTextDocument>
41 #include <QtGui/QTextCursor>
42 #include <QtGui/QTextBlock>
43
44 using namespace QmlJSEditor;
45 using namespace Internal;
46 using namespace QmlJS;
47
48 static int blockStartState(const QTextBlock &block)
49 {
50     int state = block.previous().userState();
51
52     if (state == -1)
53         return 0;
54     else
55         return state & 0xff;
56 }
57
58 static Token tokenUnderCursor(const QTextCursor &cursor)
59 {
60     const QString blockText = cursor.block().text();
61     const int blockState = blockStartState(cursor.block());
62
63     Scanner tokenize;
64     const QList<Token> tokens = tokenize(blockText, blockState);
65     const int pos = cursor.positionInBlock();
66
67     int tokenIndex = 0;
68     for (; tokenIndex < tokens.size(); ++tokenIndex) {
69         const Token &token = tokens.at(tokenIndex);
70
71         if (token.is(Token::Comment) || token.is(Token::String)) {
72             if (pos > token.begin() && pos <= token.end())
73                 break;
74         } else {
75             if (pos >= token.begin() && pos < token.end())
76                 break;
77         }
78     }
79
80     if (tokenIndex != tokens.size())
81         return tokens.at(tokenIndex);
82
83     return Token();
84 }
85
86 static bool shouldInsertMatchingText(QChar lookAhead)
87 {
88     switch (lookAhead.unicode()) {
89     case '{': case '}':
90     case ']': case ')':
91     case ';': case ',':
92     case '"': case '\'':
93         return true;
94
95     default:
96         if (lookAhead.isSpace())
97             return true;
98
99         return false;
100     } // switch
101 }
102
103 static bool shouldInsertMatchingText(const QTextCursor &tc)
104 {
105     QTextDocument *doc = tc.document();
106     return shouldInsertMatchingText(doc->characterAt(tc.selectionEnd()));
107 }
108
109 static bool shouldInsertNewline(const QTextCursor &tc)
110 {
111     QTextDocument *doc = tc.document();
112     int pos = tc.selectionEnd();
113
114     // count the number of empty lines.
115     int newlines = 0;
116     for (int e = doc->characterCount(); pos != e; ++pos) {
117         const QChar ch = doc->characterAt(pos);
118
119         if (! ch.isSpace())
120             break;
121         else if (ch == QChar::ParagraphSeparator)
122             ++newlines;
123     }
124
125     if (newlines <= 1 && doc->characterAt(pos) != QLatin1Char('}'))
126         return true;
127
128     return false;
129 }
130
131 static bool isCompleteStringLiteral(const QStringRef &text)
132 {
133     if (text.length() < 2)
134         return false;
135
136     const QChar quote = text.at(0);
137
138     if (text.at(text.length() - 1) == quote)
139         return text.at(text.length() - 2) != QLatin1Char('\\'); // ### not exactly.
140
141     return false;
142 }
143
144 AutoCompleter::AutoCompleter()
145 {}
146
147 AutoCompleter::~AutoCompleter()
148 {}
149
150 bool AutoCompleter::contextAllowsAutoParentheses(const QTextCursor &cursor,
151                                                  const QString &textToInsert) const
152 {
153     QChar ch;
154
155     if (! textToInsert.isEmpty())
156         ch = textToInsert.at(0);
157
158     switch (ch.unicode()) {
159     case '\'':
160     case '"':
161
162     case '(':
163     case '[':
164     case '{':
165
166     case ')':
167     case ']':
168     case '}':
169
170     case ';':
171         break;
172
173     default:
174         if (ch.isNull())
175             break;
176
177         return false;
178     } // end of switch
179
180     const Token token = tokenUnderCursor(cursor);
181     switch (token.kind) {
182     case Token::Comment:
183         return false;
184
185     case Token::String: {
186         const QString blockText = cursor.block().text();
187         const QStringRef tokenText = blockText.midRef(token.offset, token.length);
188         QChar quote = tokenText.at(0);
189         // if a string literal doesn't start with a quote, it must be multiline
190         if (quote != QLatin1Char('"') && quote != QLatin1Char('\'')) {
191             const int startState = blockStartState(cursor.block());
192             if (startState == Scanner::MultiLineStringDQuote)
193                 quote = QLatin1Char('"');
194             else if (startState == Scanner::MultiLineStringSQuote)
195                 quote = QLatin1Char('\'');
196         }
197
198         // never insert ' into string literals, it adds spurious ' when writing contractions
199         if (ch == QLatin1Char('\''))
200             return false;
201
202         if (ch != quote || isCompleteStringLiteral(tokenText))
203             break;
204
205         return false;
206     }
207
208     default:
209         break;
210     } // end of switch
211
212     return true;
213 }
214
215 bool AutoCompleter::contextAllowsElectricCharacters(const QTextCursor &cursor) const
216 {
217     Token token = tokenUnderCursor(cursor);
218     switch (token.kind) {
219     case Token::Comment:
220     case Token::String:
221         return false;
222     default:
223         return true;
224     }
225 }
226
227 bool AutoCompleter::isInComment(const QTextCursor &cursor) const
228 {
229     return tokenUnderCursor(cursor).is(Token::Comment);
230 }
231
232 QString AutoCompleter::insertMatchingBrace(const QTextCursor &cursor,
233                                            const QString &text,
234                                            QChar,
235                                            int *skippedChars) const
236 {
237     if (text.length() != 1)
238         return QString();
239
240     if (! shouldInsertMatchingText(cursor))
241         return QString();
242
243     const QChar la = cursor.document()->characterAt(cursor.position());
244
245     const QChar ch = text.at(0);
246     switch (ch.unicode()) {
247     case '\'':
248         if (la != ch)
249             return QString(ch);
250         ++*skippedChars;
251         break;
252
253     case '"':
254         if (la != ch)
255             return QString(ch);
256         ++*skippedChars;
257         break;
258
259     case '(':
260         return QString(QLatin1Char(')'));
261
262     case '[':
263         return QString(QLatin1Char(']'));
264
265     case '{':
266         return QString(); // nothing to do.
267
268     case ')':
269     case ']':
270     case '}':
271     case ';':
272         if (la == ch)
273             ++*skippedChars;
274         break;
275
276     default:
277         break;
278     } // end of switch
279
280     return QString();
281 }
282
283 QString AutoCompleter::insertParagraphSeparator(const QTextCursor &cursor) const
284 {
285     if (shouldInsertNewline(cursor)) {
286         QTextCursor selCursor = cursor;
287         selCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
288         if (! selCursor.selectedText().trimmed().isEmpty())
289             return QString();
290
291         return QLatin1String("}\n");
292     }
293
294     return QLatin1String("}");
295 }