358 lines
9.6 KiB
C++
358 lines
9.6 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2006-2009 fullmetalcoder <fullmetalcoder@hotmail.fr>
|
|
**
|
|
** This file is part of the Edyuk project <http://edyuk.org>
|
|
**
|
|
** This file may be used under the terms of the GNU General Public License
|
|
** version 3 as published by the Free Software Foundation and appearing in the
|
|
** file GPL.txt included in the packaging of this file.
|
|
**
|
|
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
**
|
|
****************************************************************************/
|
|
|
|
/*!
|
|
\file qsnippet.cpp
|
|
\brief Implementation of the builtin snippet types and loaders
|
|
*/
|
|
|
|
#include "qsnippet_p.h"
|
|
|
|
#include <QMap>
|
|
|
|
/*!
|
|
\class QSnippet
|
|
\brief The base class for snippets
|
|
*/
|
|
|
|
/*!
|
|
\class QSnippetPatternLoader
|
|
\brief The base class for snippet loaders
|
|
*/
|
|
|
|
QSnippetInsertionCommand::QSnippetInsertionCommand(QEditor *e)
|
|
: QDocumentCommandBlock(e->document()), m_editor(e), m_cursor(e->cursor()) {
|
|
}
|
|
|
|
QSnippetInsertionCommand::~QSnippetInsertionCommand() {
|
|
foreach (const QEditor::PlaceHolder &ph, m_placeHolders)
|
|
delete ph.affector;
|
|
}
|
|
|
|
void QSnippetInsertionCommand::addPlaceHolder(const QEditor::PlaceHolder &ph) {
|
|
m_placeHolders << ph;
|
|
}
|
|
|
|
void QSnippetInsertionCommand::addCommand(QDocumentCommand *c) {
|
|
c->setTargetCursor(m_cursor.handle());
|
|
QDocumentCommandBlock::addCommand(c);
|
|
}
|
|
|
|
void QSnippetInsertionCommand::removeCommand(QDocumentCommand *c) {
|
|
c->setTargetCursor(0);
|
|
QDocumentCommandBlock::removeCommand(c);
|
|
}
|
|
|
|
void QSnippetInsertionCommand::redo() {
|
|
m_editor->clearPlaceHolders();
|
|
QDocumentCommandBlock::redo();
|
|
|
|
foreach (const QEditor::PlaceHolder &ph, m_placeHolders)
|
|
m_editor->addPlaceHolder(ph);
|
|
|
|
m_editor->nextPlaceHolder();
|
|
}
|
|
|
|
void QSnippetInsertionCommand::undo() {
|
|
// TODO : backup and restore previous placeholders?
|
|
m_editor->clearPlaceHolders();
|
|
QDocumentCommandBlock::undo();
|
|
m_editor->setCursor(m_cursor);
|
|
}
|
|
|
|
//
|
|
|
|
bool QCE::Snippets::PlainText::loadSnippet(QSnippet *snip,
|
|
const QString &pattern) {
|
|
PlainText *target = dynamic_cast<PlainText *>(snip);
|
|
|
|
if (!target) {
|
|
qWarning("snippet/loader type mismatch.");
|
|
return false;
|
|
}
|
|
|
|
target->m_data = pattern;
|
|
|
|
return true;
|
|
}
|
|
|
|
void QCE::Snippets::PlainText::insert(QEditor *e) const {
|
|
/*
|
|
QDocumentCursor c = e->cursor();
|
|
c.insertText(m_data);
|
|
e->setCursor(c);
|
|
*/
|
|
|
|
e->write(m_data);
|
|
}
|
|
|
|
//
|
|
|
|
QString parsePlaceHolder(const QString &s, int &index, int max,
|
|
QMap<int, QCE::Snippets::Simple::PlaceHolder> &p,
|
|
int &line, int &column, int baseSize) {
|
|
QChar c;
|
|
QStringList segments;
|
|
int i = index, depth = 1, last = index + 1;
|
|
|
|
while (i + 1 < max) {
|
|
c = s.at(++i);
|
|
|
|
if (c == QLatin1Char('{')) {
|
|
++depth;
|
|
} else if (c == QLatin1Char('}')) {
|
|
--depth;
|
|
|
|
if (!depth) {
|
|
segments << s.mid(last, i - last);
|
|
break;
|
|
}
|
|
} else if (c == QLatin1Char(':')) {
|
|
if (depth == 1) {
|
|
segments << s.mid(last, i - last);
|
|
last = i + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (segments.isEmpty()) {
|
|
qWarning("invalid placeholder");
|
|
return QString();
|
|
}
|
|
|
|
int id = segments.at(0).toInt();
|
|
|
|
QCE::Snippets::Simple::PlaceHolder &ph = p[id];
|
|
|
|
if (ph.length == -1 && segments.count() > 1) {
|
|
// new placeholder
|
|
ph.length = segments.last().size();
|
|
ph.lineOffset = line;
|
|
ph.columnOffset = column;
|
|
ph.defaultValue = segments.last();
|
|
// TODO : support recursive snippetting of default value...
|
|
} else {
|
|
// mirror of an existing placeholder
|
|
QCE::Snippets::Simple::Anchor a;
|
|
a.lineOffset = line;
|
|
a.columnOffset = column;
|
|
if (ph.defaultValue.isEmpty())
|
|
ph.unresolvedMirrors << baseSize << ph.mirrors.count();
|
|
ph.mirrors << a;
|
|
}
|
|
|
|
index = i + 1;
|
|
return ph.defaultValue;
|
|
}
|
|
|
|
void performRelocation(QCE::Snippets::Simple::Anchor &a,
|
|
const QHash<int, QList<int>> &relocationTable,
|
|
int length) {
|
|
QHash<int, QList<int>>::const_iterator reloc =
|
|
relocationTable.constFind(a.lineOffset);
|
|
|
|
if (reloc == relocationTable.constEnd())
|
|
return;
|
|
|
|
int idx = 0;
|
|
int relocOffset = 0;
|
|
const QList<int> &offsets = *reloc;
|
|
|
|
while (((idx + 1) < offsets.count()) &&
|
|
(offsets.at(idx) <= a.columnOffset)) {
|
|
int off = offsets.at(++idx);
|
|
|
|
if (offsets.at(idx - 1) < a.columnOffset || off != length)
|
|
relocOffset += off;
|
|
|
|
++idx;
|
|
}
|
|
|
|
a.columnOffset += relocOffset;
|
|
}
|
|
|
|
bool QCE::Snippets::Simple::loadSnippet(QSnippet *snip,
|
|
const QString &pattern) {
|
|
Simple *target = dynamic_cast<Simple *>(snip);
|
|
|
|
if (!target) {
|
|
qWarning("snippet/loader type mismatch");
|
|
return false;
|
|
}
|
|
|
|
target->m_base.clear();
|
|
target->m_placeHolders.clear();
|
|
|
|
int index = 0, line = 0, column = 0, max = pattern.length();
|
|
|
|
QString tmp;
|
|
QStringList base;
|
|
QMap<int, PlaceHolder> p;
|
|
|
|
while (index < max) {
|
|
QChar c = pattern.at(index);
|
|
|
|
if (c == QLatin1Char('$')) {
|
|
base << tmp;
|
|
tmp.clear();
|
|
|
|
c = pattern.at(++index);
|
|
|
|
if (c == QLatin1Char('{')) {
|
|
QString val = parsePlaceHolder(pattern, index, max, p, line,
|
|
column, base.count());
|
|
base << val;
|
|
|
|
if (val.length()) {
|
|
int nl = val.count(QLatin1Char('\n'));
|
|
|
|
line += nl;
|
|
|
|
if (nl)
|
|
column = val.length() -
|
|
val.lastIndexOf(QLatin1Char('\n')) - 1;
|
|
else
|
|
column += val.length();
|
|
}
|
|
continue;
|
|
} else {
|
|
if (c != QLatin1Char('$')) {
|
|
c = pattern.at(--index);
|
|
}
|
|
|
|
++column;
|
|
}
|
|
} else if (c == QLatin1Char('\n')) {
|
|
column = 0;
|
|
++line;
|
|
} else {
|
|
++column;
|
|
}
|
|
|
|
tmp += c;
|
|
++index;
|
|
}
|
|
|
|
if (tmp.length())
|
|
base << tmp;
|
|
|
|
QHash<int, QList<int>> relocationTable;
|
|
QMap<int, PlaceHolder>::iterator it = p.begin();
|
|
|
|
// first : build relocation table (in case several placeholders are on same
|
|
// line
|
|
while (it != p.end()) {
|
|
if (it->unresolvedMirrors.count() && it->length) {
|
|
for (int i = 0; i + 1 < it->unresolvedMirrors.count(); ++i) {
|
|
int idx = it->unresolvedMirrors.at(i);
|
|
int anchor = it->unresolvedMirrors.at(++i);
|
|
|
|
base[idx] = it->defaultValue;
|
|
|
|
const Anchor &a = it->mirrors.at(anchor);
|
|
relocationTable[a.lineOffset] << a.columnOffset << it->length;
|
|
}
|
|
|
|
it->unresolvedMirrors.clear();
|
|
}
|
|
|
|
++it;
|
|
}
|
|
|
|
it = p.begin();
|
|
|
|
// then : apply relocation and store the corrected placeholder data
|
|
while (it != p.end()) {
|
|
performRelocation(*it, relocationTable, it->length);
|
|
|
|
for (int i = 0; i < it->mirrors.count(); ++i)
|
|
performRelocation(it->mirrors[i], relocationTable, it->length);
|
|
|
|
target->m_placeHolders << *it;
|
|
++it;
|
|
}
|
|
|
|
target->m_base = base.join(QString());
|
|
|
|
return true;
|
|
}
|
|
|
|
void QCE::Snippets::Simple::insert(QEditor *e) const {
|
|
// TODO : move into command and backup for proper undo/redo
|
|
e->clearPlaceHolders();
|
|
|
|
QDocument *d = e->document();
|
|
QDocumentCursor c = e->cursor();
|
|
|
|
if (c.isNull())
|
|
c = QDocumentCursor(d);
|
|
|
|
int line = qMax(c.lineNumber(), 0), column = qMax(c.columnNumber(), 0);
|
|
|
|
if (line != c.lineNumber() || column != c.columnNumber())
|
|
c = QDocumentCursor(d, line, column);
|
|
|
|
QSnippetInsertionCommand *cmd = new QSnippetInsertionCommand(e);
|
|
|
|
QDocumentCommand *scmd = 0;
|
|
|
|
if (c.hasSelection()) {
|
|
QDocumentSelection sel = c.selection();
|
|
|
|
// qDebug("((%i, %i), (%i, %i))", sel.startLine, sel.start, sel.endLine,
|
|
// sel.end);
|
|
scmd = new QDocumentEraseCommand(sel.startLine, sel.start, sel.endLine,
|
|
sel.end, d);
|
|
|
|
cmd->addCommand(scmd);
|
|
|
|
line = sel.startLine;
|
|
column = sel.start;
|
|
}
|
|
|
|
// qDebug("%s", qPrintable(m_base));
|
|
|
|
if (scmd) {
|
|
// trick to get insert command to init properly
|
|
scmd->redo();
|
|
}
|
|
|
|
cmd->addCommand(new QDocumentInsertCommand(line, column, m_base, d));
|
|
|
|
if (scmd) {
|
|
// trick to get insert command to init properly
|
|
scmd->undo();
|
|
}
|
|
|
|
if (m_placeHolders.count()) {
|
|
foreach (const PlaceHolder &ph, m_placeHolders) {
|
|
QEditor::PlaceHolder eph;
|
|
eph.length = ph.length;
|
|
eph.cursor =
|
|
QDocumentCursor(d, line + ph.lineOffset,
|
|
ph.columnOffset + (ph.lineOffset ? 0 : column));
|
|
// eph.affector = new StubAffector;
|
|
foreach (const Anchor &a, ph.mirrors)
|
|
eph.mirrors << QDocumentCursor(d, line + a.lineOffset,
|
|
a.columnOffset +
|
|
(a.lineOffset ? 0 : column));
|
|
|
|
cmd->addPlaceHolder(eph);
|
|
}
|
|
}
|
|
|
|
d->execute(cmd);
|
|
}
|