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