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 <qqmlformatting_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/qqmldomoutwriter_p.h>
11#include <QtQmlFormat/private/qqmlformatsettings_p.h>
12
13QT_BEGIN_NAMESPACE
14
15Q_LOGGING_CATEGORY(formatLog, "qt.languageserver.formatting")
16
17QQmlDocumentFormatting::QQmlDocumentFormatting(QmlLsp::QQmlCodeModel *codeModel)
18 : QQmlBaseModule(codeModel)
19{
20}
21
22QString QQmlDocumentFormatting::name() const
23{
24 return u"QQmlDocumentFormatting"_s;
25}
26
27void QQmlDocumentFormatting::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
28{
29 protocol->registerDocumentFormattingRequestHandler(handler: getRequestHandler());
30}
31
32void QQmlDocumentFormatting::setupCapabilities(
33 const QLspSpecification::InitializeParams &,
34 QLspSpecification::InitializeResult &serverCapabilities)
35{
36 // TODO: Allow customized formatting in future
37 serverCapabilities.capabilities.documentFormattingProvider = true;
38}
39
40void QQmlDocumentFormatting::process(RequestPointerArgument request)
41{
42 QList<QLspSpecification::TextEdit> result;
43 ResponseScopeGuard guard(result, request->m_response);
44
45 using namespace QQmlJS::Dom;
46 QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
47 url: QQmlLSUtils::lspUriToQmlUrl(uri: request->m_parameters.textDocument.uri));
48
49 DomItem file = doc.snapshot.doc.fileObject(option: GoTo::MostLikely);
50 if (!file) {
51 guard.setError(QQmlLSUtils::ErrorMessage{
52 .code: 0, .message: u"Could not find the file %1"_s.arg(a: doc.snapshot.doc.canonicalFilePath()) });
53 return;
54 }
55 if (!file.field(name: Fields::isValid).value().toBool(defaultValue: false)) {
56 guard.setError(QQmlLSUtils::ErrorMessage{ .code: 0, .message: u"Cannot format invalid documents!"_s });
57 return;
58 }
59 if (auto envPtr = file.environment().ownerAs<DomEnvironment>())
60 envPtr->clearReferenceCache();
61
62 auto qmlFile = file.ownerAs<QmlFile>();
63 if (!qmlFile || !qmlFile->isValid()) {
64 file.iterateErrors(
65 visitor: [](const DomItem &, const ErrorMessage &msg) {
66 errorToQDebug(msg);
67 return true;
68 },
69 iterate: true);
70 guard.setError(QQmlLSUtils::ErrorMessage{
71 .code: 0, .message: u"Failed to parse %1"_s.arg(a: file.canonicalFilePath()) });
72 return;
73 }
74
75 // TODO: implement formatting options via LSP
76 // For now, qmlformat's default options via m_formatOptions and read .qmlformat.ini
77 QQmlFormatSettings settings(QLatin1String("qmlformat"));
78 settings.search(path: qmlFile->canonicalFilePath());
79
80 QQmlFormatOptions currentFormatOptions = m_formatOptions;
81 currentFormatOptions.applySettings(settings);
82
83 QLspSpecification::TextEdit formattedText;
84 LineWriter lw([&formattedText](QStringView s) { formattedText.newText += s.toUtf8(); },
85 QString(), currentFormatOptions.optionsForCode(code: qmlFile->code()));
86 OutWriter ow(lw);
87 file.writeOutForFile(ow, extraChecks: WriteOutCheck::None);
88 ow.flush();
89 const auto &code = qmlFile->code();
90 const auto [endLine, endColumn] = QQmlLSUtils::textRowAndColumnFrom(code, offset: code.length());
91
92 Q_UNUSED(endColumn);
93 formattedText.range = QLspSpecification::Range{ .start: QLspSpecification::Position{ .line: 0, .character: 0 },
94 .end: QLspSpecification::Position{ .line: endLine + 1, .character: 0 } };
95
96 result.append(t: formattedText);
97}
98
99QT_END_NAMESPACE
100

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