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