Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlrangeformatting.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
5#include <qqmlrangeformatting_p.h>
6#include <qqmlcodemodel_p.h>
7#include <qqmllsutils_p.h>
8
9#include <QtQmlDom/private/qqmldomitem_p.h>
10#include <QtQmlDom/private/qqmldomindentinglinewriter_p.h>
11#include <QtQmlDom/private/qqmldomcodeformatter_p.h>
12#include <QtQmlDom/private/qqmldomoutwriter_p.h>
13#include <QtQmlDom/private/qqmldommock_p.h>
14#include <QtQmlDom/private/qqmldomcompare_p.h>
15
17
18QQmlRangeFormatting::QQmlRangeFormatting(QmlLsp::QQmlCodeModelManager *codeModelManager)
19 : QQmlBaseModule(codeModelManager)
20{
21}
22
23QString QQmlRangeFormatting::name() const
24{
25 return u"QQmlRangeFormatting"_s;
26}
27
28void QQmlRangeFormatting::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
29{
30 protocol->registerDocumentRangeFormattingRequestHandler(getRequestHandler());
31}
32
33void QQmlRangeFormatting::setupCapabilities(const QLspSpecification::InitializeParams &,
34 QLspSpecification::InitializeResult &serverCapabilities)
35{
36 serverCapabilities.capabilities.documentRangeFormattingProvider = true;
37}
38
39void QQmlRangeFormatting::process(RequestPointerArgument request)
40{
41 using namespace QQmlJS::Dom;
42 QList<QLspSpecification::TextEdit> result{};
43
44 QmlLsp::OpenDocument doc = m_codeModelManager->openDocumentByUrl(
45 QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri));
46
47 DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
48 if (!file) {
49 qWarning() << u"Could not find the file"_s << doc.snapshot.doc.toString();
50 return;
51 }
52
53 if (auto envPtr = file.environment().ownerAs<DomEnvironment>())
54 envPtr->clearReferenceCache();
55
56 auto qmlFile = file.ownerAs<QmlFile>();
57 auto code = qmlFile->code();
58
59 // Range requested to be formatted
60 const auto selectedRange = request->m_parameters.range;
61 const auto selectedRangeStartLine = selectedRange.start.line;
62 const auto selectedRangeEndLine = selectedRange.end.line;
63 Q_ASSERT(selectedRangeStartLine >= 0);
64 Q_ASSERT(selectedRangeEndLine >= 0);
65
66 LineWriterOptions options;
67 options.attributesSequence = LineWriterOptions::AttributesSequence::Preserve;
68
69 QTextStream in(&code);
70 FormatTextStatus status = FormatTextStatus::initialStatus();
71 FormatPartialStatus partialStatus({}, options.formatOptions, status);
72
73 // Get the token status of the previous line without performing write operation
74 int lineNumber = 0;
75 while (!in.atEnd()) {
76 const auto line = in.readLine();
77 partialStatus = formatCodeLine(line, options.formatOptions, partialStatus.currentStatus);
78 if (++lineNumber >= selectedRangeStartLine)
79 break;
80 }
81
82 QString resultText;
83 QTextStream out(&resultText);
84 IndentingLineWriter lw([&out](QStringView writtenText) { out << writtenText.toUtf8(); },
85 QString(), options, partialStatus.currentStatus);
86 OutWriter ow(qmlFile, lw);
87 ow.indentNextlines = true;
88
89 // TODO: This is a workaround and will/should be handled by the actual formatter
90 // once we improve the range-formatter design in QTBUG-116139
91 const auto removeSpaces = [](const QString &line) {
92 QString result;
93 QTextStream out(&result);
94 bool previousIsSpace = false;
95
96 int newLineCount = 0;
97 for (int i = 0; i < line.length(); ++i) {
98 QChar c = line.at(i);
99 if (c.isSpace()) {
100 if (c == '\n'_L1 && newLineCount < 2) {
101 out << '\n'_L1;
102 ++newLineCount;
103 } else if (c == '\r'_L1 && (i + 1) < line.length() && line.at(i + 1) == '\n'_L1
104 && newLineCount < 2) {
105 out << "\r\n";
106 ++newLineCount;
107 ++i;
108 } else {
109 if (!previousIsSpace)
110 out << ' '_L1;
111 }
112 previousIsSpace = true;
113 } else {
114 out << c;
115 previousIsSpace = false;
116 newLineCount = 0;
117 }
118 }
119
120 out.flush();
121 return result;
122 };
123
124 const auto startOffset = QQmlLSUtils::textOffsetFrom(code, selectedRangeStartLine, 0);
125 auto endOffset = QQmlLSUtils::textOffsetFrom(code, selectedRangeEndLine + 1, 0);
126
127 // note: the character at endOffset (usually a \n) is ignored. Therefore avoid
128 // formatting \r\n that will ignore \n and format \r as a space (' ').
129 if (endOffset < code.size() && code[endOffset - 1] == u'\r' && code[endOffset] == u'\n')
130 --endOffset;
131
132 const auto &toFormat = code.mid(startOffset, endOffset - startOffset);
133 ow.write(removeSpaces(toFormat));
134 ow.flush();
135 ow.eof();
136
137 const auto documentLineCount = QQmlLSUtils::textRowAndColumnFrom(code, code.length()).line;
138 code.replace(startOffset, toFormat.length(), resultText);
139
140 QLspSpecification::TextEdit add;
141 add.newText = code.toUtf8();
142 add.range = { { 0, 0 }, { documentLineCount + 1 } };
143 result.append(add);
144
145 request->m_response.sendResponse(result);
146}
147
148QT_END_NAMESPACE
Implements a server for the language server protocol.
void process(RequestPointerArgument req) override
void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, QLspSpecification::InitializeResult &) override
void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override