| 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 |  |