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 /*!
17 	\file qsnippet.cpp
18 	\brief Implementation of the builtin snippet types and loaders
19 */
20 
21 #include "qsnippet_p.h"
22 
23 #include <QMap>
24 
25 /*!
26 	\class QSnippet
27 	\brief The base class for snippets
28 */
29 
30 /*!
31 	\class QSnippetPatternLoader
32 	\brief The base class for snippet loaders
33 */
34 
QSnippetInsertionCommand(QEditor * e)35 QSnippetInsertionCommand::QSnippetInsertionCommand(QEditor *e)
36  : QDocumentCommandBlock(e->document()), m_editor(e), m_cursor(e->cursor())
37 {
38 }
39 
~QSnippetInsertionCommand()40 QSnippetInsertionCommand::~QSnippetInsertionCommand()
41 {
42 	foreach ( const QEditor::PlaceHolder& ph, m_placeHolders )
43 		delete ph.affector;
44 }
45 
addPlaceHolder(const QEditor::PlaceHolder & ph)46 void QSnippetInsertionCommand::addPlaceHolder(const QEditor::PlaceHolder& ph)
47 {
48 	m_placeHolders << ph;
49 }
50 
addCommand(QDocumentCommand * c)51 void QSnippetInsertionCommand::addCommand(QDocumentCommand *c)
52 {
53 	c->setTargetCursor(m_cursor.handle());
54 	QDocumentCommandBlock::addCommand(c);
55 }
56 
removeCommand(QDocumentCommand * c)57 void QSnippetInsertionCommand::removeCommand(QDocumentCommand *c)
58 {
59 	c->setTargetCursor(0);
60 	QDocumentCommandBlock::removeCommand(c);
61 }
62 
redo()63 void QSnippetInsertionCommand::redo()
64 {
65 	m_editor->clearPlaceHolders();
66 	QDocumentCommandBlock::redo();
67 
68 	foreach ( const QEditor::PlaceHolder& ph, m_placeHolders )
69 		m_editor->addPlaceHolder(ph);
70 
71 	m_editor->nextPlaceHolder();
72 }
73 
undo()74 void QSnippetInsertionCommand::undo()
75 {
76 	// TODO : backup and restore previous placeholders?
77 	m_editor->clearPlaceHolders();
78 	QDocumentCommandBlock::undo();
79 	m_editor->setCursor(m_cursor);
80 }
81 
82 //
83 
loadSnippet(QSnippet * snip,const QString & pattern)84 bool QCE::Snippets::PlainText::loadSnippet(QSnippet *snip, const QString& pattern)
85 {
86 	PlainText *target = dynamic_cast<PlainText*>(snip);
87 
88 	if ( !target )
89 	{
90 		qWarning("snippet/loader type mismatch.");
91 		return false;
92 	}
93 
94 	target->m_data = pattern;
95 
96 	return true;
97 }
98 
insert(QEditor * e) const99 void QCE::Snippets::PlainText::insert(QEditor *e) const
100 {
101 	/*
102 	QDocumentCursor c = e->cursor();
103 	c.insertText(m_data);
104 	e->setCursor(c);
105 	*/
106 
107 	e->write(m_data);
108 }
109 
110 //
111 
parsePlaceHolder(const QString & s,int & index,int max,QMap<int,QCE::Snippets::Simple::PlaceHolder> & p,int & line,int & column,int baseSize)112 QString parsePlaceHolder(const QString& s, int& index, int max, QMap<int, QCE::Snippets::Simple::PlaceHolder>& p, int& line, int& column, int baseSize)
113 {
114 	QChar c;
115 	QStringList segments;
116 	int i = index, depth = 1, last = index + 1;
117 
118 	while ( i + 1 < max )
119 	{
120 		c = s.at(++i);
121 
122 		if ( c == QLatin1Char('{') )
123 		{
124 			++depth;
125 		} else if ( c == QLatin1Char('}') ) {
126 			--depth;
127 
128 			if ( !depth )
129 			{
130 				segments << s.mid(last, i - last);
131 				break;
132 			}
133 		} else if ( c == QLatin1Char(':') ) {
134 			if ( depth == 1 )
135 			{
136 				segments << s.mid(last, i - last);
137 				last = i + 1;
138 			}
139 		}
140 	}
141 
142 	if ( segments.isEmpty() )
143 	{
144 		qWarning("invalid placeholder");
145 		return QString();
146 	}
147 
148 	int id = segments.at(0).toInt();
149 
150 	QCE::Snippets::Simple::PlaceHolder& ph = p[id];
151 
152 	if ( ph.length == -1 && segments.count() > 1 )
153 	{
154 		// new placeholder
155 		ph.length = segments.last().count();
156 		ph.lineOffset = line;
157 		ph.columnOffset = column;
158 		ph.defaultValue = segments.last();
159 		// TODO : support recursive snippetting of default value...
160 	} else {
161 		// mirror of an existing placeholder
162 		QCE::Snippets::Simple::Anchor a;
163 		a.lineOffset = line;
164 		a.columnOffset = column;
165 		if ( ph.defaultValue.isEmpty() )
166 			ph.unresolvedMirrors << baseSize << ph.mirrors.count();
167 		ph.mirrors << a;
168 	}
169 
170 	index = i + 1;
171 	return ph.defaultValue;
172 }
173 
performRelocation(QCE::Snippets::Simple::Anchor & a,const QHash<int,QList<int>> & relocationTable,int length)174 void performRelocation(QCE::Snippets::Simple::Anchor& a, const QHash<int, QList<int> >& relocationTable, int length)
175 {
176 	QHash<int, QList<int> >::const_iterator reloc = relocationTable.constFind(a.lineOffset);
177 
178 	if ( reloc == relocationTable.constEnd() )
179 		return;
180 
181 	int idx = 0;
182 	int relocOffset = 0;
183 	const QList<int>& offsets = *reloc;
184 
185 	while ( ((idx + 1) < offsets.count()) && (offsets.at(idx) <= a.columnOffset) )
186 	{
187 		int off = offsets.at(++idx);
188 
189 		if ( offsets.at(idx - 1) < a.columnOffset || off != length )
190 			relocOffset += off;
191 
192 		++idx;
193 	}
194 
195 	a.columnOffset += relocOffset;
196 }
197 
loadSnippet(QSnippet * snip,const QString & pattern)198 bool QCE::Snippets::Simple::loadSnippet(QSnippet *snip, const QString& pattern)
199 {
200 	Simple *target = dynamic_cast<Simple*>(snip);
201 
202 	if ( !target )
203 	{
204 		qWarning("snippet/loader type mismatch");
205 		return false;
206 	}
207 
208 	target->m_base.clear();
209 	target->m_placeHolders.clear();
210 
211 	int index = 0, line = 0, column = 0, max = pattern.length();
212 
213 	QString tmp;
214 	QStringList base;
215 	QMap<int, PlaceHolder> p;
216 
217 	while ( index < max )
218 	{
219 		QChar c = pattern.at(index);
220 
221 		if ( c == QLatin1Char('$') )
222 		{
223 			base << tmp;
224 			tmp.clear();
225 
226 			c = pattern.at(++index);
227 
228 			if ( c == QLatin1Char('{') )
229 			{
230 				QString val = parsePlaceHolder(pattern, index, max, p, line, column, base.count());
231 				base << val;
232 
233 				if ( val.count() )
234 				{
235 					int nl = val.count(QLatin1Char('\n'));
236 
237 					line += nl;
238 
239 					if ( nl )
240 						column = val.count() - val.lastIndexOf(QLatin1Char('\n')) - 1;
241 					else
242 						column += val.count();
243 				}
244 				continue;
245 			} else {
246 				if ( c != QLatin1Char('$') )
247 				{
248 					c = pattern.at(--index);
249 				}
250 
251 				++column;
252 			}
253 		} else if ( c == QLatin1Char('\n') ) {
254 			column = 0;
255 			++line;
256 		} else {
257 			++column;
258 		}
259 
260 		tmp += c;
261 		++index;
262 	}
263 
264 	if ( tmp.count() )
265 		base << tmp;
266 
267 	QHash<int, QList<int> > relocationTable;
268 	QMap<int, PlaceHolder>::iterator it = p.begin();
269 
270 	// first : build relocation table (in case several placeholders are on same line
271 	while ( it != p.end() )
272 	{
273 		if ( it->unresolvedMirrors.count() && it->length )
274 		{
275 			for ( int i = 0; i + 1 < it->unresolvedMirrors.count(); ++i )
276 			{
277 				int idx = it->unresolvedMirrors.at(i);
278 				int anchor = it->unresolvedMirrors.at(++i);
279 
280 				base[idx] = it->defaultValue;
281 
282 				const Anchor& a = it->mirrors.at(anchor);
283 				relocationTable[a.lineOffset] << a.columnOffset << it->length;
284 			}
285 
286 			it->unresolvedMirrors.clear();
287 		}
288 
289 		++it;
290 	}
291 
292 	it = p.begin();
293 
294 	// then : apply relocation and store the corrected placeholder data
295 	while ( it != p.end() )
296 	{
297 		performRelocation(*it, relocationTable, it->length);
298 
299 		for ( int i = 0; i < it->mirrors.count(); ++i )
300 			performRelocation(it->mirrors[i], relocationTable, it->length);
301 
302 		target->m_placeHolders << *it;
303 		++it;
304 	}
305 
306 	target->m_base = base.join(QString::null);
307 
308 	return true;
309 }
310 
insert(QEditor * e) const311 void QCE::Snippets::Simple::insert(QEditor *e) const
312 {
313 	// TODO : move into command and backup for proper undo/redo
314 	e->clearPlaceHolders();
315 
316 	QDocument *d = e->document();
317 	QDocumentCursor c = e->cursor();
318 
319 	if ( c.isNull() )
320 		c = QDocumentCursor(d);
321 
322 	int line = qMax(c.lineNumber(), 0), column = qMax(c.columnNumber(), 0);
323 
324 	if ( line != c.lineNumber() || column != c.columnNumber() )
325 		c = QDocumentCursor(d, line, column);
326 
327 	QSnippetInsertionCommand *cmd = new QSnippetInsertionCommand(e);
328 
329 	QDocumentCommand *scmd = 0;
330 
331 	if ( c.hasSelection() )
332 	{
333 		QDocumentSelection sel = c.selection();
334 
335 		//qDebug("((%i, %i), (%i, %i))", sel.startLine, sel.start, sel.endLine, sel.end);
336 		scmd = new QDocumentEraseCommand(sel.startLine, sel.start, sel.endLine, sel.end, d);
337 
338 		cmd->addCommand(scmd);
339 
340 		line = sel.startLine;
341 		column = sel.start;
342 	}
343 
344 	//qDebug("%s", qPrintable(m_base));
345 
346 	if ( scmd )
347 	{
348 		// trick to get insert command to init properly
349 		scmd->redo();
350 	}
351 
352 	cmd->addCommand(new QDocumentInsertCommand(line, column, m_base, d));
353 
354 	if ( scmd )
355 	{
356 		// trick to get insert command to init properly
357 		scmd->undo();
358 	}
359 
360 	if ( m_placeHolders.count() )
361 	{
362 		foreach ( const PlaceHolder& ph, m_placeHolders )
363 		{
364 			QEditor::PlaceHolder eph;
365 			eph.length = ph.length;
366 			eph.cursor = QDocumentCursor(d, line + ph.lineOffset, ph.columnOffset + (ph.lineOffset ? 0 : column));
367 			//eph.affector = new StubAffector;
368 			foreach ( const Anchor& a, ph.mirrors )
369 				eph.mirrors << QDocumentCursor(d, line + a.lineOffset, a.columnOffset + (a.lineOffset ? 0 : column));
370 
371 			cmd->addPlaceHolder(eph);
372 		}
373 	}
374 
375 	d->execute(cmd);
376 }
377