1 /****************************************************************************
2 **
3 ** Copyright (C) 2006-2009 fullmetalcoder <fullmetalcoder@hotmail.fr>
4 **
5 ** This file is part of the Edyuk project <http://edyuk.org>
6 **
7 ** This file may be used under the terms of the GNU General Public License
8 ** version 3 as published by the Free Software Foundation and appearing in the
9 ** file GPL.txt included in the packaging of this file.
10 **
11 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13 **
14 ****************************************************************************/
15
16 #include "qcodecompletionengine.h"
17
18 /*!
19 \file qcompletionengine.cpp
20 \brief Implementation of the QCodeCompletionEngine class.
21 */
22
23 #include "qeditor.h"
24
25 #ifdef _QCODE_MODEL_
26 #include "qcodebuffer.h"
27 #endif
28
29 /*!
30
31 */
QCodeCompletionEngine(QObject * p)32 QCodeCompletionEngine::QCodeCompletionEngine(QObject *p)
33 : QObject(p), m_max(0)
34 {
35 pForcedTrigger = new QAction(tr("&Trigger completion"), this);
36
37 connect(pForcedTrigger , SIGNAL( triggered() ),
38 this , SLOT ( complete() ) );
39
40 }
41
42 /*!
43
44 */
~QCodeCompletionEngine()45 QCodeCompletionEngine::~QCodeCompletionEngine()
46 {
47
48 }
49
50 /*!
51 \return
52 */
triggerAction() const53 QAction* QCodeCompletionEngine::triggerAction() const
54 {
55 return pForcedTrigger;
56 }
57
58 /*!
59
60 */
retranslate()61 void QCodeCompletionEngine::retranslate()
62 {
63 pForcedTrigger->setText(tr("&Trigger completion"));
64 }
65
66 /*!
67
68 */
triggers() const69 QStringList QCodeCompletionEngine::triggers() const
70 {
71 return m_triggers;
72 }
73
74 /*!
75
76 */
addTrigger(const QString & s)77 void QCodeCompletionEngine::addTrigger(const QString& s)
78 {
79 if ( m_triggers.contains(s) )
80 return;
81
82 if ( s.count() > m_max )
83 m_max = s.count();
84
85 m_triggers << s;
86 }
87
88 /*!
89
90 */
removeTrigger(const QString & s)91 void QCodeCompletionEngine::removeTrigger(const QString& s)
92 {
93 m_triggers.removeAll(s);
94 }
95
96 /*!
97
98 */
setCodeModel(QCodeModel * m)99 void QCodeCompletionEngine::setCodeModel(QCodeModel *m)
100 {
101 Q_UNUSED(m)
102 }
103
104 /*!
105
106 */
editor() const107 QEditor* QCodeCompletionEngine::editor() const
108 {
109 return pEdit;
110 }
111
112 /*!
113 \brief Attach the completion engine instance to a new editor object
114 */
setEditor(QEditor * e)115 void QCodeCompletionEngine::setEditor(QEditor *e)
116 {
117 if ( pEdit )
118 {
119 pEdit->removeAction(pForcedTrigger, "&Edit");
120 //pEdit->removeEventFilter(this);
121
122 disconnect( pEdit , SIGNAL( textEdited(QKeyEvent*) ),
123 this , SLOT ( textEdited(QKeyEvent*) ) );
124 }
125
126 pEdit = e;
127
128 if ( pEdit )
129 {
130 //pEdit->installEventFilter(this);
131 pEdit->addAction(pForcedTrigger, "&Edit");
132
133 connect(pEdit , SIGNAL( textEdited(QKeyEvent*) ),
134 this , SLOT ( textEdited(QKeyEvent*) ) );
135 }
136 }
137
138 /*!
139 \internal
140 */
run()141 void QCodeCompletionEngine::run()
142 {
143 if ( m_cur.isNull() )
144 return;
145
146 //qDebug("complete!");
147
148 complete(m_cur, m_trig);
149
150 m_cur = QDocumentCursor();
151 m_trig.clear();
152 }
153
154 /*!
155 \brief Forced completion trigger
156 */
complete()157 void QCodeCompletionEngine::complete()
158 {
159 complete(editor()->cursor(), QString());
160 }
161
162 /*!
163 \brief Standard completion entry point for QEditor
164 \param e QKeyEvent that caused a modification of the text
165
166 \note This slot is only called when editing happens without
167 any cursor mirrors
168 */
textEdited(QKeyEvent * k)169 void QCodeCompletionEngine::textEdited(QKeyEvent *k)
170 {
171 QString s, txt = s = k->text();
172 QDocumentCursor cur = editor()->cursor();
173
174 int count = txt.count();
175
176 if ( txt.isEmpty() || m_triggers.isEmpty() )
177 return;
178
179 //qDebug("should trigger completion? (bis)");
180
181 if ( count > m_max )
182 {
183 txt = txt.right(m_max);
184
185 } else if ( count < m_max ) {
186
187 QDocumentCursor c(cur);
188 c.movePosition(m_max, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
189
190 //qDebug("prev text : %s", qPrintable(c.selectedText()));
191
192 txt = c.selectedText();
193 }
194
195 //qDebug("text : %s", qPrintable(txt));
196
197 foreach ( QString trig, m_triggers )
198 {
199 if ( txt.endsWith(trig) )
200 {
201 cur = editor()->cursor();
202 cur.movePosition(trig.count(), QDocumentCursor::PreviousCharacter);
203
204 // notify completion trigger
205 emit completionTriggered(trig);
206
207 //get rid of previous calltips/completions
208 editor()->setFocus();
209
210 // trigger completion
211 complete(cur, trig);
212 }
213 }
214 }
215
216 /*!
217 \internal
218 */
eventFilter(QObject * o,QEvent * e)219 bool QCodeCompletionEngine::eventFilter(QObject *o, QEvent *e)
220 {
221 if ( !e || !o || (e->type() != QEvent::KeyPress) || (o != pEdit) )
222 return false;
223
224 //qDebug("should trigger completion?");
225
226 QDocumentCursor cur = editor()->cursor();
227 QKeyEvent *k = static_cast<QKeyEvent*>(e);
228
229 QString s, txt = s = k->text();
230
231 int count = txt.count();
232
233 if ( txt.isEmpty() || m_triggers.isEmpty() )
234 return false; // QThread::eventFilter(o, e);
235
236 //qDebug("should trigger completion? (bis)");
237
238 if ( count > m_max )
239 {
240 txt = txt.right(m_max);
241
242 } else if ( count < m_max ) {
243
244 QDocumentCursor c(cur);
245 c.movePosition(m_max - count, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
246
247 //qDebug("prev text : %s", qPrintable(c.selectedText()));
248
249 txt.prepend(c.selectedText());
250 }
251
252 //qDebug("text : %s", qPrintable(txt));
253
254 foreach ( QString trig, m_triggers )
255 {
256 if ( txt.endsWith(trig) )
257 {
258 editor()->write(s);
259
260 cur = editor()->cursor();
261 cur.movePosition(trig.count(), QDocumentCursor::PreviousCharacter);
262
263 // notify completion trigger
264 emit completionTriggered(trig);
265
266 //get rid of previous calltips/completions
267 editor()->setFocus();
268
269 // trigger completion
270 complete(cur, trig);
271
272 return true;
273 }
274 }
275
276 return false;
277 }
278
279 /*!
280 \brief Completion callback
281 */
complete(const QDocumentCursor & c,const QString & trigger)282 void QCodeCompletionEngine::complete(const QDocumentCursor& c, const QString& trigger)
283 {
284 #ifdef _QCODE_MODEL_
285 // TODO :
286 // * use a more efficient design by avoiding deep copy of the data
287 // * only lex the requested part (stop at cursor or topmost frame required for proper class hierarchy)
288
289 QDocumentCursor cc = c;
290 cc.movePosition(1, QDocumentCursor::Start, QDocumentCursor::KeepAnchor);
291
292 //qDebug("%s", qPrintable(cc.selectedText()));
293
294 QCodeBuffer buffer(cc.selectedText());
295 //QCodeBuffer buffer(c.document()->text());
296 complete(&buffer, trigger);
297 #else
298 Q_UNUSED(c)
299 Q_UNUSED(trigger)
300 qWarning("From complete(QDocumentCursor, QString)");
301 qWarning("QCodeCompletionEngine is not self-sufficient : subclasses should "
302 "reimplement at least on of the complete() method...");
303 #endif
304 }
305
306 /*!
307 \overload
308 \brief Overloaded completion callback
309 */
complete(QCodeStream * s,const QString & trigger)310 void QCodeCompletionEngine::complete(QCodeStream *s, const QString& trigger)
311 {
312 Q_UNUSED(s)
313 Q_UNUSED(trigger)
314
315 qWarning("From complete(QCodeStream*, QString)");
316 qWarning("QCodeCompletionEngine is not self-sufficient : subclasses should"
317 "reimplement at least on of the complete() method...");
318 }
319