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 | QList<QLspSpecification::TextEdit> result; |
42 | ResponseScopeGuard guard(result, request->m_response); |
43 | |
44 | using namespace QQmlJS::Dom; |
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 | guard.setError(QQmlLSUtils::ErrorMessage{ |
51 | .code: 0, .message: u"Could not find the file %1"_s .arg(a: doc.snapshot.doc.canonicalFilePath()) }); |
52 | return; |
53 | } |
54 | if (!file.field(name: Fields::isValid).value().toBool(defaultValue: false)) { |
55 | guard.setError(QQmlLSUtils::ErrorMessage{ .code: 0, .message: u"Cannot format invalid documents!"_s }); |
56 | return; |
57 | } |
58 | if (auto envPtr = file.environment().ownerAs<DomEnvironment>()) |
59 | envPtr->clearReferenceCache(); |
60 | |
61 | auto qmlFile = file.ownerAs<QmlFile>(); |
62 | if (!qmlFile || !qmlFile->isValid()) { |
63 | file.iterateErrors( |
64 | visitor: [](const DomItem &, const ErrorMessage &msg) { |
65 | errorToQDebug(msg); |
66 | return true; |
67 | }, |
68 | iterate: true); |
69 | guard.setError(QQmlLSUtils::ErrorMessage{ |
70 | .code: 0, .message: u"Failed to parse %1"_s .arg(a: file.canonicalFilePath()) }); |
71 | return; |
72 | } |
73 | |
74 | // TODO: implement formatting options |
75 | // For now, qmlformat's default options. |
76 | LineWriterOptions options; |
77 | options.updateOptions = LineWriterOptions::Update::None; |
78 | options.attributesSequence = LineWriterOptions::AttributesSequence::Preserve; |
79 | |
80 | QLspSpecification::TextEdit formattedText; |
81 | LineWriter lw([&formattedText](QStringView s) {formattedText.newText += s.toUtf8(); }, QString(), options); |
82 | OutWriter ow(lw); |
83 | file.writeOutForFile(ow, extraChecks: WriteOutCheck::None); |
84 | ow.flush(); |
85 | const auto &code = qmlFile->code(); |
86 | const auto [endLine, endColumn] = QQmlLSUtils::textRowAndColumnFrom(code, offset: code.length()); |
87 | |
88 | Q_UNUSED(endColumn); |
89 | formattedText.range = QLspSpecification::Range{ .start: QLspSpecification::Position{ .line: 0, .character: 0 }, |
90 | .end: QLspSpecification::Position{ .line: endLine + 1, .character: 0 } }; |
91 | |
92 | result.append(t: formattedText); |
93 | } |
94 | |
95 | QT_END_NAMESPACE |
96 | |