aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlls/qqmlrangeformatting.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlls/qqmlrangeformatting.cpp')
-rw-r--r--src/qmlls/qqmlrangeformatting.cpp144
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