diff options
author | Semih Yavuz <semih.yavuz@qt.io> | 2023-07-25 13:09:55 +0200 |
---|---|---|
committer | Semih Yavuz <semih.yavuz@qt.io> | 2023-08-17 10:19:48 +0200 |
commit | b6c89c0d1354f24e26c3df45a3524e363c4116bb (patch) | |
tree | f78689ee5955dc22b02f26c3e26386d916cf49d9 /src/qmlls/qqmlrangeformatting.cpp | |
parent | 4f335c11f73ac5b5c17cf75f0fef9f57d6d868fe (diff) |
qmlls: Add support for range formatting
Implement a selection based code formatter. A range
formatter is a code formatting tool that formats only a selected portion
of the code rather than the entire file. We now don't check the validity
of dom document, instead we read the source code and obtain the token
status of the previous line of the selected range. Then, indenting line
writer perfoms the indentation based on the last status.
For now, it has a limited functionality that it can only perfom
reindentation and whitespace handling. We will improve that with
QTBUG-116139
Exclude newly added test files from qmlformat test cases since currently
qmldom cannot handle unicode characters in code.
Fixes: QTBUG-112978
Change-Id: Iabf392a7b2623cfee44d042e1c87b021ccc6e5e6
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
Diffstat (limited to 'src/qmlls/qqmlrangeformatting.cpp')
-rw-r--r-- | src/qmlls/qqmlrangeformatting.cpp | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/qmlls/qqmlrangeformatting.cpp b/src/qmlls/qqmlrangeformatting.cpp new file mode 100644 index 0000000000..1bcabc190d --- /dev/null +++ b/src/qmlls/qqmlrangeformatting.cpp @@ -0,0 +1,144 @@ +// 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 + +#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 + +Q_DECLARE_LOGGING_CATEGORY(formatLog) + +QQmlRangeFormatting::QQmlRangeFormatting(QmlLsp::QQmlCodeModel *codeModel) + : QQmlBaseModule(codeModel) +{ +} + +QString QQmlRangeFormatting::name() const +{ + return u"QQmlRangeFormatting"_s; +} + +void QQmlRangeFormatting::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol) +{ + protocol->registerDocumentRangeFormattingRequestHandler(getRequestHandler()); +} + +void QQmlRangeFormatting::setupCapabilities(const QLspSpecification::InitializeParams &, + QLspSpecification::InitializeResult &serverCapabilities) +{ + serverCapabilities.capabilities.documentRangeFormattingProvider = true; +} + +void QQmlRangeFormatting::process(RequestPointerArgument request) +{ + using namespace QQmlJS::Dom; + QList<QLspSpecification::TextEdit> result{}; + + QmlLsp::OpenDocument doc = m_codeModel->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; + Q_ASSERT(selectedRangeStartLine >= 0); + Q_ASSERT(selectedRangeEndLine >= 0); + + LineWriterOptions options; + options.updateOptions = LineWriterOptions::Update::None; + 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 + int 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(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); + const auto endOffset = QQmlLSUtils::textOffsetFrom(code, selectedRangeEndLine + 1, 0); + 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 + 1 } }; + result.append(add); + + request->m_response.sendResponse(result); +} + +QT_END_NAMESPACE |