Revision control

Copy as Markdown

// Copyright (C) 2023 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
// Qt-Security score:significant reason:default
#include <qqmlrangeformatting_p.h>
#include <qqmlcodemodel_p.h>
#include <qqmllsutils_p.h>
#include <QtQmlDom/private/qqmldomitem_p.h>
#include <QtQmlDom/private/qqmldomindentinglinewriter_p.h>
#include <QtQmlDom/private/qqmldomcodeformatter_p.h>
#include <QtQmlDom/private/qqmldomoutwriter_p.h>
#include <QtQmlDom/private/qqmldommock_p.h>
#include <QtQmlDom/private/qqmldomcompare_p.h>
QT_BEGIN_NAMESPACE
QQmlRangeFormatting::QQmlRangeFormatting(QmlLsp::QQmlCodeModelManager *codeModelManager)
: QQmlBaseModule(codeModelManager)
{
}
void QQmlRangeFormatting::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
{
protocol->registerDocumentRangeFormattingRequestHandler(getRequestHandler());
}
void QQmlRangeFormatting::setupCapabilities(QLspSpecification::ServerCapabilities &caps)
{
caps.documentRangeFormattingProvider = true;
}
void QQmlRangeFormatting::process(RequestPointerArgument request)
{
using namespace QQmlJS::Dom;
QList<QLspSpecification::TextEdit> result{};
QmlLsp::OpenDocument doc = m_codeModelManager->openDocumentByUrl(
QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri));
DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
if (!file) {
qWarning() << u"Could not find the file"_s << doc.snapshot.doc.toString();
return;
}
if (auto envPtr = file.environment().ownerAs<DomEnvironment>())
envPtr->clearReferenceCache();
auto qmlFile = file.ownerAs<QmlFile>();
auto code = qmlFile->code();
// Range requested to be formatted
const auto selectedRange = request->m_parameters.range;
const auto selectedRangeStartLine = selectedRange.start.line;
const auto selectedRangeEndLine = selectedRange.end.line;
LineWriterOptions options;
options.attributesSequence = LineWriterOptions::AttributesSequence::Preserve;
QTextStream in(&code);
FormatTextStatus status = FormatTextStatus::initialStatus();
FormatPartialStatus partialStatus({}, options.formatOptions, status);
// Get the token status of the previous line without performing write operation
unsigned lineNumber = 0;
while (!in.atEnd()) {
const auto line = in.readLine();
partialStatus = formatCodeLine(line, options.formatOptions, partialStatus.currentStatus);
if (++lineNumber >= selectedRangeStartLine)
break;
}
QString resultText;
QTextStream out(&resultText);
IndentingLineWriter lw([&out](QStringView writtenText) { out << writtenText.toUtf8(); },
QString(), options, partialStatus.currentStatus);
OutWriter ow(qmlFile, lw);
ow.indentNextlines = true;
// TODO: This is a workaround and will/should be handled by the actual formatter
// once we improve the range-formatter design in QTBUG-116139
const auto removeSpaces = [](const QString &line) {
QString result;
QTextStream out(&result);
bool previousIsSpace = false;
int newLineCount = 0;
for (int i = 0; i < line.length(); ++i) {
QChar c = line.at(i);
if (c.isSpace()) {
if (c == '\n'_L1 && newLineCount < 2) {
out << '\n'_L1;
++newLineCount;
} else if (c == '\r'_L1 && (i + 1) < line.length() && line.at(i + 1) == '\n'_L1
&& newLineCount < 2) {
out << "\r\n";
++newLineCount;
++i;
} else {
if (!previousIsSpace)
out << ' '_L1;
}
previousIsSpace = true;
} else {
out << c;
previousIsSpace = false;
newLineCount = 0;
}
}
out.flush();
return result;
};
const auto startOffset = QQmlLSUtils::textOffsetFrom(code, selectedRangeStartLine, 0);
auto endOffset = QQmlLSUtils::textOffsetFrom(code, selectedRangeEndLine + 1, 0);
// note: the character at endOffset (usually a \n) is ignored. Therefore avoid
// formatting \r\n that will ignore \n and format \r as a space (' ').
if (endOffset < code.size() && code[endOffset - 1] == u'\r' && code[endOffset] == u'\n')
--endOffset;
const auto &toFormat = code.mid(startOffset, endOffset - startOffset);
ow.write(removeSpaces(toFormat));
ow.flush();
ow.eof();
const auto documentLineCount = QQmlLSUtils::textRowAndColumnFrom(code, code.length()).line;
code.replace(startOffset, toFormat.length(), resultText);
QLspSpecification::TextEdit add;
add.newText = code.toUtf8();
add.range = { { 0, 0 }, { documentLineCount + 1u } };
result.append(add);
request->m_response.sendResponse(result);
}
QT_END_NAMESPACE