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