aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlls/qqmlrangeformatting.cpp
diff options
context:
space:
mode:
authorSemih Yavuz <semih.yavuz@qt.io>2023-07-25 13:09:55 +0200
committerSemih Yavuz <semih.yavuz@qt.io>2023-08-17 10:19:48 +0200
commitb6c89c0d1354f24e26c3df45a3524e363c4116bb (patch)
treef78689ee5955dc22b02f26c3e26386d916cf49d9 /src/qmlls/qqmlrangeformatting.cpp
parent4f335c11f73ac5b5c17cf75f0fef9f57d6d868fe (diff)
qmlls: Add support for range formatting
Implement a selection based code formatter. A range formatter is a code formatting tool that formats only a selected portion of the code rather than the entire file. We now don't check the validity of dom document, instead we read the source code and obtain the token status of the previous line of the selected range. Then, indenting line writer perfoms the indentation based on the last status. For now, it has a limited functionality that it can only perfom reindentation and whitespace handling. We will improve that with QTBUG-116139 Exclude newly added test files from qmlformat test cases since currently qmldom cannot handle unicode characters in code. Fixes: QTBUG-112978 Change-Id: Iabf392a7b2623cfee44d042e1c87b021ccc6e5e6 Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
Diffstat (limited to 'src/qmlls/qqmlrangeformatting.cpp')
-rw-r--r--src/qmlls/qqmlrangeformatting.cpp144
1 files changed, 144 insertions, 0 deletions
diff --git a/src/qmlls/qqmlrangeformatting.cpp b/src/qmlls/qqmlrangeformatting.cpp
new file mode 100644
index 0000000000..1bcabc190d
--- /dev/null
+++ b/src/qmlls/qqmlrangeformatting.cpp
@@ -0,0 +1,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