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 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | Q_LOGGING_CATEGORY(formatLog, "qt.languageserver.formatting" ) |
15 | |
16 | QQmlDocumentFormatting::QQmlDocumentFormatting(QmlLsp::QQmlCodeModel *codeModel) |
17 | : QQmlBaseModule(codeModel) |
18 | { |
19 | } |
20 | |
21 | QString QQmlDocumentFormatting::name() const |
22 | { |
23 | return u"QQmlDocumentFormatting"_s ; |
24 | } |
25 | |
26 | void QQmlDocumentFormatting::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol) |
27 | { |
28 | protocol->registerDocumentFormattingRequestHandler(handler: getRequestHandler()); |
29 | } |
30 | |
31 | void QQmlDocumentFormatting::setupCapabilities( |
32 | const QLspSpecification::InitializeParams &, |
33 | QLspSpecification::InitializeResult &serverCapabilities) |
34 | { |
35 | // TODO: Allow customized formatting in future |
36 | serverCapabilities.capabilities.documentFormattingProvider = true; |
37 | } |
38 | |
39 | void QQmlDocumentFormatting::process(RequestPointerArgument request) |
40 | { |
41 | using namespace QQmlJS::Dom; |
42 | QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl( |
43 | url: QQmlLSUtils::lspUriToQmlUrl(uri: request->m_parameters.textDocument.uri)); |
44 | |
45 | DomItem file = doc.snapshot.doc.fileObject(option: GoTo::MostLikely); |
46 | if (!file) { |
47 | qWarning() << u"Could not find the file"_s << doc.snapshot.doc.toString(); |
48 | return; |
49 | } |
50 | if (!file.field(name: Fields::isValid).value().toBool(defaultValue: false)) { |
51 | qWarning() << u"Invalid document will not be formatted"_s ; |
52 | return; |
53 | } |
54 | if (auto envPtr = file.environment().ownerAs<DomEnvironment>()) |
55 | envPtr->clearReferenceCache(); |
56 | |
57 | auto qmlFile = file.ownerAs<QmlFile>(); |
58 | if (!qmlFile || !qmlFile->isValid()) { |
59 | file.iterateErrors( |
60 | visitor: [](DomItem, ErrorMessage msg) { |
61 | errorToQDebug(msg); |
62 | return true; |
63 | }, |
64 | iterate: true); |
65 | qWarning().noquote() << "Failed to parse" << file; |
66 | return; |
67 | } |
68 | |
69 | // TODO: implement formatting options |
70 | // For now, qmlformat's default options. |
71 | LineWriterOptions options; |
72 | options.updateOptions = LineWriterOptions::Update::None; |
73 | options.attributesSequence = LineWriterOptions::AttributesSequence::Preserve; |
74 | |
75 | QLspSpecification::TextEdit formattedText; |
76 | LineWriter lw([&formattedText](QStringView s) {formattedText.newText += s.toUtf8(); }, QString(), options); |
77 | OutWriter ow(lw); |
78 | MutableDomItem result = file.writeOutForFile(ow, extraChecks: WriteOutCheck::Default); |
79 | ow.flush(); |
80 | |
81 | const auto &code = qmlFile->code(); |
82 | const auto [endLine, endColumn] = QQmlLSUtils::textRowAndColumnFrom(code, offset: code.length()); |
83 | |
84 | Q_UNUSED(endColumn); |
85 | formattedText.range = QLspSpecification::Range{ .start: QLspSpecification::Position{ .line: 0, .character: 0 }, |
86 | .end: QLspSpecification::Position{ .line: endLine + 1, .character: 0 } }; |
87 | |
88 | request->m_response.sendResponse(r: QList<QLspSpecification::TextEdit>{ formattedText }); |
89 | } |
90 | |
91 | QT_END_NAMESPACE |
92 | |