aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlls/qqmlrangeformatting.cpp
blob: 1bcabc190da6ccb6120647c4a397aaca0f8cee21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <qqmlrangeformatting_p.h>
#include <qqmlcodemodel_p.h>
#include <qqmllsutils_p.h>

#include <QtQmlDom/private/qqmldomitem_p.h>
#include <QtQmlDom/private/qqmldomindentinglinewriter_p.h>
#include <QtQmlDom/private/qqmldomcodeformatter_p.h>
#include <QtQmlDom/private/qqmldomoutwriter_p.h>
#include <QtQmlDom/private/qqmldommock_p.h>
#include <QtQmlDom/private/qqmldomcompare_p.h>

QT_BEGIN_NAMESPACE

Q_DECLARE_LOGGING_CATEGORY(formatLog)

QQmlRangeFormatting::QQmlRangeFormatting(QmlLsp::QQmlCodeModel *codeModel)
    : QQmlBaseModule(codeModel)
{
}

QString QQmlRangeFormatting::name() const
{
    return u"QQmlRangeFormatting"_s;
}

void QQmlRangeFormatting::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
{
    protocol->registerDocumentRangeFormattingRequestHandler(getRequestHandler());
}

void QQmlRangeFormatting::setupCapabilities(const QLspSpecification::InitializeParams &,
                                            QLspSpecification::InitializeResult &serverCapabilities)
{
    serverCapabilities.capabilities.documentRangeFormattingProvider = true;
}

void QQmlRangeFormatting::process(RequestPointerArgument request)
{
    using namespace QQmlJS::Dom;
    QList<QLspSpecification::TextEdit> result{};

    QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
            QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri));

    DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
    if (!file) {
        qWarning() << u"Could not find the file"_s << doc.snapshot.doc.toString();
        return;
    }

    if (auto envPtr = file.environment().ownerAs<DomEnvironment>())
        envPtr->clearReferenceCache();

    auto qmlFile = file.ownerAs<QmlFile>();
    auto code = qmlFile->code();

    // Range requested to be formatted
    const auto selectedRange = request->m_parameters.range;
    const auto selectedRangeStartLine = selectedRange.start.line;
    const auto selectedRangeEndLine = selectedRange.end.line;
    Q_ASSERT(selectedRangeStartLine >= 0);
    Q_ASSERT(selectedRangeEndLine >= 0);

    LineWriterOptions options;
    options.updateOptions = LineWriterOptions::Update::None;
    options.attributesSequence = LineWriterOptions::AttributesSequence::Preserve;

    QTextStream in(&code);
    FormatTextStatus status = FormatTextStatus::initialStatus();
    FormatPartialStatus partialStatus({}, options.formatOptions, status);

    // Get the token status of the previous line without performing write operation
    int lineNumber = 0;
    while (!in.atEnd()) {
        const auto line = in.readLine();
        partialStatus = formatCodeLine(line, options.formatOptions, partialStatus.currentStatus);
        if (++lineNumber >= selectedRangeStartLine)
            break;
    }

    QString resultText;
    QTextStream out(&resultText);
    IndentingLineWriter lw([&out](QStringView writtenText) { out << writtenText.toUtf8(); },
                           QString(), options, partialStatus.currentStatus);
    OutWriter ow(lw);
    ow.indentNextlines = true;

    // TODO: This is a workaround and will/should be handled by the actual formatter
    // once we improve the range-formatter design in QTBUG-116139
    const auto removeSpaces = [](const QString &line) {
        QString result;
        QTextStream out(&result);
        bool previousIsSpace = false;

        int newLineCount = 0;
        for (int i = 0; i < line.length(); ++i) {
            QChar c = line.at(i);
            if (c.isSpace()) {
                if (c == '\n'_L1 && newLineCount < 2) {
                    out << '\n'_L1;
                    ++newLineCount;
                } else if (c == '\r'_L1 && (i + 1) < line.length() && line.at(i + 1) == '\n'_L1
                           && newLineCount < 2) {
                    out << "\r\n";
                    ++newLineCount;
                    ++i;
                } else {
                    if (!previousIsSpace)
                        out << ' '_L1;
                }
                previousIsSpace = true;
            } else {
                out << c;
                previousIsSpace = false;
                newLineCount = 0;
            }
        }

        out.flush();
        return result;
    };

    const auto startOffset = QQmlLSUtils::textOffsetFrom(code, selectedRangeStartLine, 0);
    const auto endOffset = QQmlLSUtils::textOffsetFrom(code, selectedRangeEndLine + 1, 0);
    const auto &toFormat = code.mid(startOffset, endOffset - startOffset);
    ow.write(removeSpaces(toFormat));
    ow.flush();
    ow.eof();

    const auto documentLineCount = QQmlLSUtils::textRowAndColumnFrom(code, code.length()).line;
    code.replace(startOffset, toFormat.length(), resultText);

    QLspSpecification::TextEdit add;
    add.newText = code.toUtf8();
    add.range = { { 0, 0 }, { documentLineCount + 1 } };
    result.append(add);

    request->m_response.sendResponse(result);
}

QT_END_NAMESPACE