1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qqmlrangeformatting_p.h>
5#include <qqmlcodemodel_p.h>
6#include <qqmllsutils_p.h>
7
8#include <QtQmlDom/private/qqmldomitem_p.h>
9#include <QtQmlDom/private/qqmldomindentinglinewriter_p.h>
10#include <QtQmlDom/private/qqmldomcodeformatter_p.h>
11#include <QtQmlDom/private/qqmldomoutwriter_p.h>
12#include <QtQmlDom/private/qqmldommock_p.h>
13#include <QtQmlDom/private/qqmldomcompare_p.h>
14
15QT_BEGIN_NAMESPACE
16
17Q_DECLARE_LOGGING_CATEGORY(formatLog)
18
19QQmlRangeFormatting::QQmlRangeFormatting(QmlLsp::QQmlCodeModel *codeModel)
20 : QQmlBaseModule(codeModel)
21{
22}
23
24QString QQmlRangeFormatting::name() const
25{
26 return u"QQmlRangeFormatting"_s;
27}
28
29void QQmlRangeFormatting::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
30{
31 protocol->registerDocumentRangeFormattingRequestHandler(handler: getRequestHandler());
32}
33
34void QQmlRangeFormatting::setupCapabilities(const QLspSpecification::InitializeParams &,
35 QLspSpecification::InitializeResult &serverCapabilities)
36{
37 serverCapabilities.capabilities.documentRangeFormattingProvider = true;
38}
39
40void QQmlRangeFormatting::process(RequestPointerArgument request)
41{
42 using namespace QQmlJS::Dom;
43 QList<QLspSpecification::TextEdit> result{};
44
45 QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
46 url: QQmlLSUtils::lspUriToQmlUrl(uri: request->m_parameters.textDocument.uri));
47
48 DomItem file = doc.snapshot.doc.fileObject(option: GoTo::MostLikely);
49 if (!file) {
50 qWarning() << u"Could not find the file"_s << doc.snapshot.doc.toString();
51 return;
52 }
53
54 if (auto envPtr = file.environment().ownerAs<DomEnvironment>())
55 envPtr->clearReferenceCache();
56
57 auto qmlFile = file.ownerAs<QmlFile>();
58 auto code = qmlFile->code();
59
60 // Range requested to be formatted
61 const auto selectedRange = request->m_parameters.range;
62 const auto selectedRangeStartLine = selectedRange.start.line;
63 const auto selectedRangeEndLine = selectedRange.end.line;
64 Q_ASSERT(selectedRangeStartLine >= 0);
65 Q_ASSERT(selectedRangeEndLine >= 0);
66
67 LineWriterOptions options;
68 options.updateOptions = LineWriterOptions::Update::None;
69 options.attributesSequence = LineWriterOptions::AttributesSequence::Preserve;
70
71 QTextStream in(&code);
72 FormatTextStatus status = FormatTextStatus::initialStatus();
73 FormatPartialStatus partialStatus({}, options.formatOptions, status);
74
75 // Get the token status of the previous line without performing write operation
76 int lineNumber = 0;
77 while (!in.atEnd()) {
78 const auto line = in.readLine();
79 partialStatus = formatCodeLine(line, options: options.formatOptions, initialStatus: partialStatus.currentStatus);
80 if (++lineNumber >= selectedRangeStartLine)
81 break;
82 }
83
84 QString resultText;
85 QTextStream out(&resultText);
86 IndentingLineWriter lw([&out](QStringView writtenText) { out << writtenText.toUtf8(); },
87 QString(), options, partialStatus.currentStatus);
88 OutWriter ow(lw);
89 ow.indentNextlines = true;
90
91 // TODO: This is a workaround and will/should be handled by the actual formatter
92 // once we improve the range-formatter design in QTBUG-116139
93 const auto removeSpaces = [](const QString &line) {
94 QString result;
95 QTextStream out(&result);
96 bool previousIsSpace = false;
97
98 int newLineCount = 0;
99 for (int i = 0; i < line.length(); ++i) {
100 QChar c = line.at(i);
101 if (c.isSpace()) {
102 if (c == '\n'_L1 && newLineCount < 2) {
103 out << '\n'_L1;
104 ++newLineCount;
105 } else if (c == '\r'_L1 && (i + 1) < line.length() && line.at(i: i + 1) == '\n'_L1
106 && newLineCount < 2) {
107 out << "\r\n";
108 ++newLineCount;
109 ++i;
110 } else {
111 if (!previousIsSpace)
112 out << ' '_L1;
113 }
114 previousIsSpace = true;
115 } else {
116 out << c;
117 previousIsSpace = false;
118 newLineCount = 0;
119 }
120 }
121
122 out.flush();
123 return result;
124 };
125
126 const auto startOffset = QQmlLSUtils::textOffsetFrom(code, row: selectedRangeStartLine, character: 0);
127 const auto endOffset = QQmlLSUtils::textOffsetFrom(code, row: selectedRangeEndLine + 1, character: 0);
128 const auto &toFormat = code.mid(position: startOffset, n: endOffset - startOffset);
129 ow.write(v: removeSpaces(toFormat));
130 ow.flush();
131 ow.eof();
132
133 const auto documentLineCount = QQmlLSUtils::textRowAndColumnFrom(code, offset: code.length()).line;
134 code.replace(i: startOffset, len: toFormat.length(), after: resultText);
135
136 QLspSpecification::TextEdit add;
137 add.newText = code.toUtf8();
138 add.range = { .start: { .line: 0, .character: 0 }, .end: { .line: documentLineCount + 1 } };
139 result.append(t: add);
140
141 request->m_response.sendResponse(r: result);
142}
143
144QT_END_NAMESPACE
145

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/qmlls/qqmlrangeformatting.cpp