1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtSCriptTools module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qscriptcompletiontask_p.h" |
41 | #include "qscriptcompletiontaskinterface_p_p.h" |
42 | #include "qscriptdebuggerconsole_p.h" |
43 | #include "qscriptdebuggerconsolecommand_p.h" |
44 | #include "qscriptdebuggerconsolecommandmanager_p.h" |
45 | #include "qscriptdebuggercommandschedulerjob_p.h" |
46 | #include "qscriptdebuggercommandschedulerfrontend_p.h" |
47 | #include "qscriptdebuggerjobschedulerinterface_p.h" |
48 | #include "qscriptdebuggerresponse_p.h" |
49 | |
50 | #include "private/qobject_p.h" |
51 | |
52 | #include <QtCore/qset.h> |
53 | #include <QtCore/qdebug.h> |
54 | |
55 | #include <algorithm> |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | class QScriptCompletionTaskPrivate |
60 | : public QScriptCompletionTaskInterfacePrivate |
61 | { |
62 | Q_DECLARE_PUBLIC(QScriptCompletionTask) |
63 | public: |
64 | QScriptCompletionTaskPrivate(); |
65 | ~QScriptCompletionTaskPrivate(); |
66 | |
67 | void completeScriptExpression(); |
68 | void emitFinished(); |
69 | |
70 | QString contents; |
71 | int cursorPosition; |
72 | int frameIndex; |
73 | QScriptDebuggerCommandSchedulerInterface *commandScheduler; |
74 | QScriptDebuggerJobSchedulerInterface *jobScheduler; |
75 | QScriptDebuggerConsole *console; |
76 | }; |
77 | |
78 | QScriptCompletionTaskPrivate::QScriptCompletionTaskPrivate() |
79 | : cursorPosition(0), frameIndex(0), commandScheduler(0), |
80 | jobScheduler(0), console(0) |
81 | { |
82 | } |
83 | |
84 | QScriptCompletionTaskPrivate::~QScriptCompletionTaskPrivate() |
85 | { |
86 | } |
87 | |
88 | class QScriptCompleteExpressionJob : public QScriptDebuggerCommandSchedulerJob |
89 | { |
90 | public: |
91 | QScriptCompleteExpressionJob(int frameIndex, const QStringList &path, |
92 | QScriptCompletionTaskPrivate *task, |
93 | QScriptDebuggerCommandSchedulerInterface *scheduler) |
94 | : QScriptDebuggerCommandSchedulerJob(scheduler), |
95 | m_frameIndex(frameIndex), m_path(path), m_task(task) |
96 | {} |
97 | |
98 | void start() |
99 | { |
100 | QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
101 | frontend.scheduleGetCompletions(contextIndex: m_frameIndex, path: m_path); |
102 | } |
103 | void handleResponse(const QScriptDebuggerResponse &response, int /*commandId*/) |
104 | { |
105 | m_task->results = response.result().toStringList(); |
106 | m_task->emitFinished(); |
107 | finish(); |
108 | } |
109 | |
110 | private: |
111 | int m_frameIndex; |
112 | QStringList m_path; |
113 | QScriptCompletionTaskPrivate *m_task; |
114 | }; |
115 | |
116 | namespace { |
117 | |
118 | static bool isIdentChar(const QChar &ch) |
119 | { |
120 | static QChar underscore = QLatin1Char('_'); |
121 | return ch.isLetterOrNumber() || (ch == underscore); |
122 | } |
123 | |
124 | static bool isPrefixOf(const QString &prefix, const QString &what) |
125 | { |
126 | return ((what.length() > prefix.length()) |
127 | && what.startsWith(s: prefix)); |
128 | } |
129 | |
130 | } // namespace |
131 | |
132 | class QScriptCompleteScriptsJob : public QScriptDebuggerCommandSchedulerJob |
133 | { |
134 | public: |
135 | QScriptCompleteScriptsJob(const QString &prefix, QScriptCompletionTaskPrivate *task, |
136 | QScriptDebuggerCommandSchedulerInterface *scheduler) |
137 | : QScriptDebuggerCommandSchedulerJob(scheduler), |
138 | m_prefix(prefix), m_task(task) |
139 | {} |
140 | |
141 | void start() |
142 | { |
143 | QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
144 | frontend.scheduleGetScripts(); |
145 | } |
146 | void handleResponse(const QScriptDebuggerResponse &response, int /*commandId*/) |
147 | { |
148 | QScriptScriptMap scripts = response.resultAsScripts(); |
149 | QScriptScriptMap::const_iterator it; |
150 | for (it = scripts.constBegin(); it != scripts.constEnd(); ++it) { |
151 | QString fileName = it.value().fileName(); |
152 | if (isPrefixOf(prefix: m_prefix, what: fileName)) |
153 | m_task->results.append(t: fileName); |
154 | } |
155 | m_task->emitFinished(); |
156 | finish(); |
157 | } |
158 | private: |
159 | QString m_prefix; |
160 | QScriptCompletionTaskPrivate *m_task; |
161 | }; |
162 | |
163 | void QScriptCompletionTaskPrivate::completeScriptExpression() |
164 | { |
165 | int pos = cursorPosition; |
166 | if ((pos > 0) && contents.at(i: pos-1).isNumber()) { |
167 | // completion of numbers is pointless |
168 | emitFinished(); |
169 | return; |
170 | } |
171 | |
172 | while ((pos > 0) && isIdentChar(ch: contents.at(i: pos-1))) |
173 | --pos; |
174 | int pos2 = cursorPosition - 1; |
175 | while ((pos2+1 < contents.size()) && isIdentChar(ch: contents.at(i: pos2+1))) |
176 | ++pos2; |
177 | QString ident = contents.mid(position: pos, n: pos2 - pos + 1); |
178 | position = pos; |
179 | |
180 | QStringList path; |
181 | path.append(t: ident); |
182 | while ((pos > 0) && (contents.at(i: pos-1) == QLatin1Char('.'))) { |
183 | --pos; |
184 | pos2 = pos; |
185 | while ((pos > 0) && isIdentChar(ch: contents.at(i: pos-1))) |
186 | --pos; |
187 | path.prepend(t: contents.mid(position: pos, n: pos2 - pos)); |
188 | } |
189 | |
190 | length = path.last().length(); |
191 | type = QScriptCompletionTask::ScriptIdentifierCompletion; |
192 | |
193 | QScriptDebuggerJob *job = new QScriptCompleteExpressionJob(frameIndex, path, this, commandScheduler); |
194 | jobScheduler->scheduleJob(job); |
195 | } |
196 | |
197 | void QScriptCompletionTaskPrivate::emitFinished() |
198 | { |
199 | emit q_func()->finished(); |
200 | } |
201 | |
202 | QScriptCompletionTask::QScriptCompletionTask( |
203 | const QString &contents, int cursorPosition, int frameIndex, |
204 | QScriptDebuggerCommandSchedulerInterface *commandScheduler, |
205 | QScriptDebuggerJobSchedulerInterface *jobScheduler, |
206 | QScriptDebuggerConsole *console, |
207 | QObject *parent) |
208 | : QScriptCompletionTaskInterface( |
209 | *new QScriptCompletionTaskPrivate, parent) |
210 | { |
211 | Q_D(QScriptCompletionTask); |
212 | d->contents = contents; |
213 | d->cursorPosition = cursorPosition; |
214 | if ((frameIndex == -1) && console) |
215 | d->frameIndex = console->currentFrameIndex(); |
216 | else |
217 | d->frameIndex = frameIndex; |
218 | d->commandScheduler = commandScheduler; |
219 | d->jobScheduler = jobScheduler; |
220 | d->console = console; |
221 | } |
222 | |
223 | QScriptCompletionTask::~QScriptCompletionTask() |
224 | { |
225 | } |
226 | |
227 | void QScriptCompletionTask::start() |
228 | { |
229 | Q_D(QScriptCompletionTask); |
230 | d->type = NoCompletion; |
231 | // see if we're typing a command |
232 | // ### don't hardcode the command prefix |
233 | QRegExp cmdRx(QString::fromLatin1(str: "^\\s*\\.([a-zA-Z]*)" )); |
234 | int cmdIndex = cmdRx.indexIn(str: d->contents); |
235 | if ((cmdIndex != -1) && d->console) { |
236 | int len = cmdRx.matchedLength(); |
237 | QString prefix = cmdRx.capturedTexts().at(i: 1); |
238 | if ((d->cursorPosition >= cmdIndex) && (d->cursorPosition <= (cmdIndex+len))) { |
239 | // editing command --> get command completions |
240 | d->results = d->console->commandManager()->completions(prefix); |
241 | d->position = cmdRx.pos(nth: 1); |
242 | d->length = prefix.length(); |
243 | d->type = CommandNameCompletion; |
244 | d->appendix = QString::fromLatin1(str: " " ); |
245 | emit finished(); |
246 | } else { |
247 | QScriptDebuggerConsoleCommand *cmd = d->console->commandManager()->findCommand(name: prefix); |
248 | if (!cmd) { |
249 | emit finished(); |
250 | return; |
251 | } |
252 | // editing an argument |
253 | int argNum = 0; |
254 | QString arg; |
255 | int pos = cmdIndex + len; |
256 | while (pos < d->contents.size()) { |
257 | while ((pos < d->contents.size()) && d->contents.at(i: pos).isSpace()) |
258 | ++pos; |
259 | if (pos < d->contents.size()) { |
260 | int pos2 = pos + 1; |
261 | while ((pos2 < d->contents.size()) && !d->contents.at(i: pos2).isSpace()) |
262 | ++pos2; |
263 | if ((d->cursorPosition >= pos) && (d->cursorPosition <= pos2)) { |
264 | arg = d->contents.mid(position: pos, n: pos2 - pos); |
265 | break; |
266 | } |
267 | pos = pos2; |
268 | ++argNum; |
269 | } |
270 | } |
271 | QString argType = cmd->argumentTypes().value(i: argNum); |
272 | if (!argType.isEmpty()) { |
273 | if (argType == QLatin1String("command-or-group-name" )) { |
274 | d->results = d->console->commandManager()->completions(prefix: arg); |
275 | } else if (argType == QLatin1String("script-filename" )) { |
276 | d->position = pos; |
277 | d->length = arg.length(); |
278 | d->type = CommandArgumentCompletion; |
279 | QScriptDebuggerJob *job = new QScriptCompleteScriptsJob(arg, d, d->commandScheduler); |
280 | d->jobScheduler->scheduleJob(job); |
281 | } else if (argType == QLatin1String("subcommand-name" )) { |
282 | for (int i = 0; i < cmd->subCommands().size(); ++i) { |
283 | QString name = cmd->subCommands().at(i); |
284 | if (isPrefixOf(prefix: arg, what: name)) |
285 | d->results.append(t: name); |
286 | } |
287 | std::stable_sort(first: d->results.begin(), last: d->results.end()); |
288 | } else if (argType == QLatin1String("script" )) { |
289 | d->completeScriptExpression(); |
290 | } else { |
291 | emit finished(); |
292 | } |
293 | if ((d->type == NoCompletion) && !d->results.isEmpty()) { |
294 | d->position = pos; |
295 | d->length = arg.length(); |
296 | d->type = CommandArgumentCompletion; |
297 | emit finished(); |
298 | } |
299 | } |
300 | } |
301 | } else { |
302 | // assume it's an eval expression |
303 | d->completeScriptExpression(); |
304 | } |
305 | } |
306 | |
307 | QT_END_NAMESPACE |
308 | |