diff options
Diffstat (limited to 'src/qmldom/qqmldomlinewriter.cpp')
-rw-r--r-- | src/qmldom/qqmldomlinewriter.cpp | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/src/qmldom/qqmldomlinewriter.cpp b/src/qmldom/qqmldomlinewriter.cpp new file mode 100644 index 0000000000..33326cb01a --- /dev/null +++ b/src/qmldom/qqmldomlinewriter.cpp @@ -0,0 +1,456 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qqmldomlinewriter_p.h" +#include <QtCore/QCoreApplication> +#include <QtCore/QRegularExpression> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +quint32 PendingSourceLocation::utf16Start() const +{ + return value.offset; +} + +quint32 PendingSourceLocation::utf16End() const +{ + return value.offset + value.length; +} + +void PendingSourceLocation::changeAtOffset(quint32 offset, qint32 change, qint32 colChange, + qint32 lineChange) +{ + if (offset < utf16Start()) { + if (change < 0 && offset - change >= utf16Start()) { + int c1 = offset - utf16Start(); + int c2 = offset - change - utf16Start(); + change = c1; + if (value.length < quint32(c2)) + value.length = 0; + else + value.length -= c2; + } + value.offset += change; + value.startColumn += colChange; + value.startLine += lineChange; + } else if (offset < utf16End()) { + if (change < 0 && offset - change > utf16End()) + change = offset - utf16End(); + value.length += change; + } +} + +void PendingSourceLocation::commit() +{ + if (toUpdate) + *toUpdate = value; + if (updater) + updater(value); +} + +LineWriter::LineWriter( + const SinkF &innerSink, const QString &fileName, const LineWriterOptions &options, + int lineNr, int columnNr, int utf16Offset, const QString ¤tLine) + : m_innerSinks({ innerSink }), + m_fileName(fileName), + m_lineNr(lineNr), + m_columnNr(columnNr), + m_currentColumnNr(columnNr), + m_utf16Offset(utf16Offset), + m_currentLine(currentLine), + m_options(options) +{ +} + +LineWriter &LineWriter::ensureNewline(int nNewline, TextAddType t) +{ + int nToAdd = nNewline; + if (nToAdd <= 0) + return *this; + if (m_currentLine.trimmed().isEmpty()) { + --nToAdd; + if (m_committedEmptyLines >= unsigned(nToAdd)) + return *this; + nToAdd -= m_committedEmptyLines; + } + for (int i = 0; i < nToAdd; ++i) + write(u"\n", t); + return *this; +} + +LineWriter &LineWriter::ensureSpace(TextAddType t) +{ + if (!m_currentLine.isEmpty() && !m_currentLine.at(m_currentLine.size() - 1).isSpace()) + write(u" ", t); + return *this; +} + +LineWriter &LineWriter::ensureSpace(QStringView space, TextAddType t) +{ + int tabSize = m_options.formatOptions.tabSize; + IndentInfo ind(space, tabSize); + auto cc = counter(); + if (ind.nNewlines > 0) + ensureNewline(ind.nNewlines, t); + if (cc != counter() || m_currentLine.isEmpty() + || !m_currentLine.at(m_currentLine.size() - 1).isSpace()) + write(ind.trailingString, t); + else { + int len = m_currentLine.size(); + int i = len; + while (i != 0 && m_currentLine.at(i - 1).isSpace()) + --i; + QStringView trailingSpace = QStringView(m_currentLine).mid(i, len - i); + int trailingSpaceStartColumn = + IndentInfo(QStringView(m_currentLine).mid(0, i), tabSize, m_columnNr).column; + IndentInfo indExisting(trailingSpace, tabSize, trailingSpaceStartColumn); + if (trailingSpaceStartColumn != 0) + ind = IndentInfo(space, tabSize, trailingSpaceStartColumn); + if (i == 0) { + if (indExisting.column < ind.column) { + qint32 utf16Change = ind.trailingString.size() - trailingSpace.size(); + m_currentColumnNr += ind.trailingString.size() - trailingSpace.size(); + m_currentLine.replace( + i, len - i, ind.trailingString.toString()); // invalidates most QStringViews + changeAtOffset(i, utf16Change, utf16Change, 0); + lineChanged(); + } + } else if (indExisting.column < ind.column) { // use just spaces if not at start of a line + write(QStringLiteral(u" ").repeated(ind.column - indExisting.column), t); + } + } + return *this; +} + +QString LineWriter::eolToWrite() const +{ + switch (m_options.lineEndings) { + case LineWriterOptions::LineEndings::Unix: + return QStringLiteral(u"\n"); + case LineWriterOptions::LineEndings::Windows: + return QStringLiteral(u"\r\n"); + case LineWriterOptions::LineEndings::OldMacOs: + return QStringLiteral(u"\r"); + } + Q_ASSERT(false); + return QStringLiteral(u"\n"); +} + +template<typename String, typename ...Args> +static QRegularExpressionMatch matchHelper(QRegularExpression &re, String &&s, Args &&...args) +{ + return re.matchView(s, args...); +} + +LineWriter &LineWriter::write(QStringView v, TextAddType tAdd) +{ + QString eol; + // split multiple lines + static QRegularExpression eolRe(QLatin1String( + "(\r?\n|\r)")); // does not support split of \r and \n for windows style line endings + QRegularExpressionMatch m = matchHelper(eolRe, v); + if (m.hasMatch()) { + // add line by line + auto i = m.capturedStart(1); + auto iEnd = m.capturedEnd(1); + eol = eolToWrite(); + // offset change (eol used vs input) cannot affect things, + // because we cannot have already opened or closed a PendingSourceLocation + if (iEnd < v.size()) { + write(v.mid(0, iEnd)); + m = matchHelper(eolRe, v, iEnd); + while (m.hasMatch()) { + write(v.mid(iEnd, m.capturedEnd(1) - iEnd)); + iEnd = m.capturedEnd(1); + m = matchHelper(eolRe, v, iEnd); + } + if (iEnd < v.size()) + write(v.mid(iEnd, v.size() - iEnd)); + return *this; + } + QStringView toAdd = v.mid(0, i); + if (!toAdd.trimmed().isEmpty()) + textAddCallback(tAdd); + m_counter += i; + m_currentLine.append(toAdd); + m_currentColumnNr += + IndentInfo(toAdd, m_options.formatOptions.tabSize, m_currentColumnNr).column; + lineChanged(); + } else { + if (!v.trimmed().isEmpty()) + textAddCallback(tAdd); + m_counter += v.size(); + m_currentLine.append(v); + m_currentColumnNr += + IndentInfo(v, m_options.formatOptions.tabSize, m_currentColumnNr).column; + lineChanged(); + } + if (!eol.isEmpty() + || (m_options.maxLineLength > 0 && m_currentColumnNr > m_options.maxLineLength)) { + reindentAndSplit(eol); + } + return *this; +} + +void LineWriter::flush() +{ + if (m_currentLine.size() > 0) + commitLine(QString()); +} + +void LineWriter::eof(bool shouldEnsureNewline) +{ + if (shouldEnsureNewline) + ensureNewline(); + reindentAndSplit(QString(), true); +} + +SourceLocation LineWriter::committedLocation() const +{ + return SourceLocation(m_utf16Offset, 0, m_lineNr, m_lineUtf16Offset); +} + +PendingSourceLocationId LineWriter::startSourceLocation(SourceLocation *toUpdate) +{ + PendingSourceLocation res; + res.id = ++m_lastSourceLocationId; + res.value = currentSourceLocation(); + res.toUpdate = toUpdate; + m_pendingSourceLocations.insert(res.id, res); + return res.id; +} + +PendingSourceLocationId LineWriter::startSourceLocation(std::function<void(SourceLocation)> updater) +{ + PendingSourceLocation res; + res.id = ++m_lastSourceLocationId; + res.value = currentSourceLocation(); + res.updater = updater; + m_pendingSourceLocations.insert(res.id, res); + return res.id; +} + +void LineWriter::endSourceLocation(PendingSourceLocationId slId) +{ + if (m_pendingSourceLocations.contains(slId)) { + auto &pLoc = m_pendingSourceLocations[slId]; + if (!pLoc.open) { + qWarning() << "Trying to close already closed PendingSourceLocation" << int(slId); + } + pLoc.open = false; + pLoc.value.length = m_utf16Offset + m_currentLine.size() - pLoc.value.offset; + } else { + qWarning() << "Trying to close non existing PendingSourceLocation" << int(slId); + } +} + +int LineWriter::addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback) +{ + int nextId = ++m_lastCallbackId; + Q_ASSERT(nextId != 0); + if (callback) + m_textAddCallbacks.insert(nextId, callback); + return nextId; +} + +int LineWriter::addNewlinesAutospacerCallback(int nLines) +{ + return addTextAddCallback([nLines](LineWriter &self, TextAddType t) { + if (t == TextAddType::Normal) { + quint32 c = self.counter(); + QString spacesToPreserve; + bool spaceOnly = QStringView(self.m_currentLine).trimmed().isEmpty(); + if (spaceOnly && !self.m_currentLine.isEmpty()) + spacesToPreserve = self.m_currentLine; + self.ensureNewline(nLines, LineWriter::TextAddType::Extra); + if (self.counter() != c && !spacesToPreserve.isEmpty()) + self.write(spacesToPreserve, TextAddType::Extra); + return false; + } else { + return true; + } + }); +} + +void LineWriter::setLineIndent(int indentAmount) +{ + int startNonSpace = 0; + while (startNonSpace < m_currentLine.size() && m_currentLine.at(startNonSpace).isSpace()) + ++startNonSpace; + int oldColumn = column(startNonSpace); + if (indentAmount >= 0) { + QString indent; + if (m_options.formatOptions.useTabs) { + indent = QStringLiteral(u"\t").repeated(indentAmount / m_options.formatOptions.tabSize) + + QStringLiteral(u" ").repeated(indentAmount % m_options.formatOptions.tabSize); + } else { + indent = QStringLiteral(u" ").repeated(indentAmount); + } + if (indent != m_currentLine.mid(0, startNonSpace)) { + quint32 colChange = indentAmount - oldColumn; + m_currentColumnNr += colChange; + qint32 oChange = indent.size() - startNonSpace; + m_currentLine = indent + m_currentLine.mid(startNonSpace); + m_currentColumnNr = column(m_currentLine.size()); + lineChanged(); + changeAtOffset(m_utf16Offset, oChange, oChange, 0); + } + } +} + +void LineWriter::handleTrailingSpace(LineWriterOptions::TrailingSpace trailingSpace) +{ + switch (trailingSpace) { + case LineWriterOptions::TrailingSpace::Preserve: + break; + case LineWriterOptions::TrailingSpace::Remove: { + int lastNonSpace = m_currentLine.size(); + while (lastNonSpace > 0 && m_currentLine.at(lastNonSpace - 1).isSpace()) + --lastNonSpace; + if (lastNonSpace != m_currentLine.size()) { + qint32 oChange = lastNonSpace - m_currentLine.size(); + m_currentLine = m_currentLine.mid(0, lastNonSpace); + changeAtOffset(m_utf16Offset + lastNonSpace, oChange, oChange, 0); + m_currentColumnNr = + column(m_currentLine.size()); // to be extra accurate in the potential split + lineChanged(); + } + } break; + } +} + +void LineWriter::reindentAndSplit(const QString &eol, bool eof) +{ + // maybe write out + if (!eol.isEmpty() || eof) { + handleTrailingSpace(m_options.codeTrailingSpace); + commitLine(eol); + } +} + +SourceLocation LineWriter::currentSourceLocation() const +{ + return SourceLocation(m_utf16Offset + m_currentLine.size(), 0, m_lineNr, + m_lineUtf16Offset + m_currentLine.size()); +} + +void LineWriter::changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange) +{ + auto iEnd = m_pendingSourceLocations.end(); + auto i = m_pendingSourceLocations.begin(); + while (i != iEnd) { + i.value().changeAtOffset(offset, change, colChange, lineChange); + ++i; + } +} + +int LineWriter::column(int index) +{ + if (index > m_currentLine.size()) + index = m_currentLine.size(); + IndentInfo iInfo(QStringView(m_currentLine).mid(0, index), m_options.formatOptions.tabSize, + m_columnNr); + return iInfo.column; +} + +void LineWriter::textAddCallback(LineWriter::TextAddType t) +{ + if (m_textAddCallbacks.isEmpty()) + return; + int iNow = (--m_textAddCallbacks.end()).key() + 1; + while (true) { + auto it = m_textAddCallbacks.lowerBound(iNow); + if (it == m_textAddCallbacks.begin()) + break; + --it; + iNow = it.key(); + if (!it.value()(*this, t)) + m_textAddCallbacks.erase(it); + } +} + +void LineWriter::commitLine(const QString &eol, TextAddType tType, int untilChar) +{ + if (untilChar == -1) + untilChar = m_currentLine.size(); + bool isSpaceOnly = QStringView(m_currentLine).mid(0, untilChar).trimmed().isEmpty(); + bool isEmptyNewline = !eol.isEmpty() && isSpaceOnly; + quint32 endCommit = m_utf16Offset + untilChar; + // update position, lineNr,... + // write out + for (SinkF &sink : m_innerSinks) + sink(m_currentLine.mid(0, untilChar)); + m_utf16Offset += untilChar; + if (!eol.isEmpty()) { + m_utf16Offset += eol.size(); + for (SinkF &sink : m_innerSinks) + sink(eol); + ++m_lineNr; + int oldCol = column(untilChar); + m_columnNr = 0; + m_lineUtf16Offset = 0; + changeAtOffset(m_utf16Offset, 0, -oldCol, 1); + } else { + m_columnNr = column(untilChar); + m_lineUtf16Offset += untilChar; + } + if (untilChar == m_currentLine.size()) { + willCommit(); + m_currentLine.clear(); + } else { + QString nextLine = m_currentLine.mid(untilChar); + m_currentLine = m_currentLine.mid(0, untilChar); + lineChanged(); + willCommit(); + m_currentLine = nextLine; + } + lineChanged(); + m_currentColumnNr = column(m_currentLine.size()); + TextAddType notifyType = tType; + switch (tType) { + case TextAddType::Normal: + if (eol.isEmpty()) + notifyType = TextAddType::PartialCommit; + else + notifyType = TextAddType::Newline; + break; + case TextAddType::Extra: + if (eol.isEmpty()) + notifyType = TextAddType::NewlineExtra; + else + notifyType = TextAddType::PartialCommit; + break; + case TextAddType::Newline: + case TextAddType::NewlineSplit: + case TextAddType::NewlineExtra: + case TextAddType::PartialCommit: + case TextAddType::Eof: + break; + } + if (isEmptyNewline) + ++m_committedEmptyLines; + else if (!isSpaceOnly) + m_committedEmptyLines = 0; + // commit finished pending + auto iEnd = m_pendingSourceLocations.end(); + auto i = m_pendingSourceLocations.begin(); + while (i != iEnd) { + auto &pLoc = i.value(); + if (!pLoc.open && pLoc.utf16End() <= endCommit) { + pLoc.commit(); + i = m_pendingSourceLocations.erase(i); + } else { + ++i; + } + } + // notify + textAddCallback(notifyType); +} + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#include "moc_qqmldomlinewriter_p.cpp" |