1// Copyright (C) 2022 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 "qqmldomindentinglinewriter_p.h"
5#include "qqmldomlinewriter_p.h"
6
7#include <QtCore/QCoreApplication>
8#include <QtCore/QRegularExpression>
9
10QT_BEGIN_NAMESPACE
11namespace QQmlJS {
12namespace Dom {
13
14// If one of these tokens is the split token on a line, the line should be split after it.
15static constexpr bool shouldSplitAfterToken(int kind)
16{
17 switch (kind) {
18 case Lexer::T_COMMA:
19 case Lexer::T_COLON:
20 case Lexer::T_SEMICOLON:
21 case Lexer::T_LBRACE:
22 case Lexer::T_LPAREN:
23 case Lexer::T_LBRACKET:
24 return true;
25 default:
26 break;
27 }
28 return false;
29}
30
31FormatPartialStatus &IndentingLineWriter::fStatus()
32{
33 if (!m_fStatusValid) {
34 m_fStatus = formatCodeLine(line: m_currentLine, options: m_options.formatOptions, initialStatus: m_preCachedStatus);
35 m_fStatusValid = true;
36 }
37 return m_fStatus;
38}
39
40void IndentingLineWriter::willCommit()
41{
42 m_preCachedStatus = fStatus().currentStatus;
43}
44
45void IndentingLineWriter::reindentAndSplit(const QString &eol, bool eof)
46{
47 // maybe re-indent
48 if (m_reindent && m_columnNr == 0)
49 setLineIndent(fStatus().indentLine());
50
51 if (!eol.isEmpty() || eof)
52 handleTrailingSpace();
53
54 // maybe split long line
55 if (m_options.maxLineLength > 0 && m_currentLine.size() > m_options.maxLineLength)
56 splitOnMaxLength(eol, eof);
57
58 // maybe write out
59 if (!eol.isEmpty() || eof)
60 commitLine(eol);
61}
62
63void IndentingLineWriter::handleTrailingSpace()
64{
65 LineWriterOptions::TrailingSpace trailingSpace;
66 if (!m_currentLine.isEmpty() && m_currentLine.trimmed().isEmpty()) {
67 // space only line
68 const Scanner::State &oldState = m_preCachedStatus.lexerState;
69 if (oldState.isMultilineComment())
70 trailingSpace = m_options.commentTrailingSpace;
71 else if (oldState.isMultiline())
72 trailingSpace = m_options.stringTrailingSpace;
73 else
74 trailingSpace = m_options.codeTrailingSpace;
75 // in the LSP we will probably want to treat is specially if it is the line with the
76 // cursor, of if indentation of it is requested
77 } else {
78 const Scanner::State &currentState = fStatus().currentStatus.lexerState;
79 if (currentState.isMultilineComment()) {
80 trailingSpace = m_options.commentTrailingSpace;
81 } else if (currentState.isMultiline()) {
82 trailingSpace = m_options.stringTrailingSpace;
83 } else {
84 const int kind =
85 (fStatus().lineTokens.isEmpty() ? Lexer::T_EOL
86 : fStatus().lineTokens.last().lexKind);
87 if (Token::lexKindIsComment(kind)) {
88 // a // comment...
89 trailingSpace = m_options.commentTrailingSpace;
90 Q_ASSERT(fStatus().currentStatus.state().type
91 != FormatTextStatus::StateType::MultilineCommentCont
92 && fStatus().currentStatus.state().type
93 != FormatTextStatus::StateType::
94 MultilineCommentStart); // these should have been
95 // handled above
96 } else {
97 trailingSpace = m_options.codeTrailingSpace;
98 }
99 }
100 }
101 LineWriter::handleTrailingSpace(s: trailingSpace);
102}
103
104int IndentingLineWriter::findSplitLocation(const QList<Token> &tokens, int minSplitLength)
105{
106 // Reverse search to find the "last"
107 auto lastDelimiterTokenBeforeLineSplitIt =
108 std::find_if(first: tokens.crbegin(), last: tokens.crend(), pred: [&](auto &&t) {
109 return Token::lexKindIsDelimiter(kind: t.lexKind)
110 && column(localIndex: t.end()) >= minSplitLength;
111 });
112
113 if (lastDelimiterTokenBeforeLineSplitIt == tokens.crend())
114 return -1;
115
116 if (shouldSplitAfterToken(lastDelimiterTokenBeforeLineSplitIt->lexKind)) {
117 return lastDelimiterTokenBeforeLineSplitIt->end();
118 } else {
119 // split before the token (after the previous token)
120 auto previousTokenIt = std::next(x: lastDelimiterTokenBeforeLineSplitIt);
121 return previousTokenIt != tokens.crend() ? previousTokenIt->end()
122 : lastDelimiterTokenBeforeLineSplitIt->begin();
123 }
124}
125
126void IndentingLineWriter::splitOnMaxLength(const QString &eol, bool eof)
127{
128 if (fStatus().lineTokens.size() <= 1)
129 return;
130 // {}[] should already be handled (handle also here?)
131 int minLen = 0;
132 while (minLen < m_currentLine.size() && m_currentLine.at(i: minLen).isSpace())
133 ++minLen;
134 minLen = column(localIndex: minLen) + m_options.minContentLength;
135 int possibleSplit = findSplitLocation(tokens: fStatus().lineTokens, minSplitLength: minLen);
136 if (possibleSplit > 0) {
137 lineChanged();
138 commitLine(eol: eolToWrite(), t: TextAddType::NewlineSplit, untilChar: possibleSplit);
139 setReindent(true);
140 reindentAndSplit(eol, eof);
141 }
142}
143
144} // namespace Dom
145} // namespace QQmlJS
146QT_END_NAMESPACE
147

source code of qtdeclarative/src/qmldom/qqmldomindentinglinewriter.cpp