Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmldomlinewriter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
5#include <QtCore/QCoreApplication>
6#include <QtCore/QRegularExpression>
7
9namespace QQmlJS {
10namespace Dom {
11
13 const SinkF &innerSink, const QString &fileName, const LineWriterOptions &options,
14 int lineNr, int columnNr, int utf16Offset, const QString &currentLine)
17 m_lineNr(lineNr),
18 m_columnNr(columnNr),
19 m_currentColumnNr(columnNr),
20 m_utf16Offset(utf16Offset),
23{
24}
25
26LineWriter &LineWriter::ensureNewline(int nNewline, TextAddType t)
27{
28 int nToAdd = nNewline;
29 if (nToAdd <= 0)
30 return *this;
31 if (m_currentLine.trimmed().isEmpty()) {
32 --nToAdd;
33 if (m_committedEmptyLines >= unsigned(nToAdd))
34 return *this;
35 nToAdd -= m_committedEmptyLines;
36 }
37 for (int i = 0; i < nToAdd; ++i)
38 write(u"\n", t);
39 return *this;
40}
41
43{
44 if (!m_currentLine.isEmpty() && !m_currentLine.at(m_currentLine.size() - 1).isSpace())
45 write(u" ", t);
46 return *this;
47}
48
50{
51 if (!m_currentLine.isEmpty() && m_currentLine.back() != u';')
52 write(u";", t);
53 return *this;
54}
55
56LineWriter &LineWriter::ensureSpace(QStringView space, TextAddType t)
57{
58 int tabSize = m_options.formatOptions.tabSize;
59 IndentInfo ind(space, tabSize);
60 auto cc = counter();
61 if (ind.nNewlines > 0)
62 ensureNewline(ind.nNewlines, t);
63 if (cc != counter() || m_currentLine.isEmpty()
64 || !m_currentLine.at(m_currentLine.size() - 1).isSpace())
65 write(ind.trailingString, t);
66 else {
67 int len = m_currentLine.size();
68 int i = len;
69 while (i != 0 && m_currentLine.at(i - 1).isSpace())
70 --i;
71 QStringView trailingSpace = QStringView(m_currentLine).mid(i, len - i);
72 int trailingSpaceStartColumn =
73 IndentInfo(QStringView(m_currentLine).mid(0, i), tabSize, m_columnNr).column;
74 IndentInfo indExisting(trailingSpace, tabSize, trailingSpaceStartColumn);
75 if (trailingSpaceStartColumn != 0)
76 ind = IndentInfo(space, tabSize, trailingSpaceStartColumn);
77 if (i == 0) {
78 if (indExisting.column < ind.column) {
79 m_currentColumnNr += ind.trailingString.size() - trailingSpace.size();
80 m_currentLine.replace(
81 i, len - i, ind.trailingString.toString()); // invalidates most QStringViews
83 }
84 } else if (indExisting.column < ind.column) { // use just spaces if not at start of a line
85 write(QStringLiteral(u" ").repeated(ind.column - indExisting.column), t);
86 }
87 }
88 return *this;
89}
90
91QString LineWriter::eolToWrite() const
92{
93 switch (m_options.lineEndings) {
94 case LineWriterOptions::LineEndings::Unix:
95 return QStringLiteral(u"\n");
96 case LineWriterOptions::LineEndings::Windows:
97 return QStringLiteral(u"\r\n");
98 case LineWriterOptions::LineEndings::OldMacOs:
99 return QStringLiteral(u"\r");
100 }
101 Q_ASSERT(false);
102 return QStringLiteral(u"\n");
103}
104
105template<typename String, typename ...Args>
106static QRegularExpressionMatch matchHelper(QRegularExpression &re, String &&s, Args &&...args)
107{
108 return re.matchView(s, args...);
109}
110
111LineWriter &LineWriter::write(QStringView v, TextAddType tAdd)
112{
113 QString eol;
114 // split multiple lines
115 static QRegularExpression eolRe(QLatin1String(
116 "(\r?\n|\r)")); // does not support split of \r and \n for windows style line endings
117 QRegularExpressionMatch m = matchHelper(eolRe, v);
118 if (m.hasMatch()) {
119 // add line by line
120 auto i = m.capturedStart(1);
121 auto iEnd = m.capturedEnd(1);
122 eol = eolToWrite();
123 // offset change (eol used vs input) cannot affect things,
124 // because we cannot have already opened or closed a PendingSourceLocation
125 if (iEnd < v.size()) {
126 write(v.mid(0, iEnd));
127 m = matchHelper(eolRe, v, iEnd);
128 while (m.hasMatch()) {
129 write(v.mid(iEnd, m.capturedEnd(1) - iEnd));
130 iEnd = m.capturedEnd(1);
131 m = matchHelper(eolRe, v, iEnd);
132 }
133 if (iEnd < v.size())
134 write(v.mid(iEnd, v.size() - iEnd));
135 return *this;
136 }
137 QStringView toAdd = v.mid(0, i);
138 if (!toAdd.trimmed().isEmpty())
139 textAddCallback(tAdd);
140 m_counter += i;
141 m_currentLine.append(toAdd);
142 m_currentColumnNr +=
143 IndentInfo(toAdd, m_options.formatOptions.tabSize, m_currentColumnNr).column;
145 } else {
146 if (!v.trimmed().isEmpty())
147 textAddCallback(tAdd);
148 m_counter += v.size();
149 m_currentLine.append(v);
150 m_currentColumnNr +=
151 IndentInfo(v, m_options.formatOptions.tabSize, m_currentColumnNr).column;
153 }
154 if (!eol.isEmpty()
155 || (m_options.maxLineLength > 0 && m_currentColumnNr > m_options.maxLineLength)) {
156 reindentAndSplit(eol);
157 }
158 return *this;
159}
160
162{
163 if (m_currentLine.size() > 0)
164 commitLine(QString());
165}
166
167void LineWriter::eof(bool shouldEnsureNewline)
168{
169 if (shouldEnsureNewline)
170 ensureNewline();
171 reindentAndSplit(QString(), true);
172}
173
175{
176 return SourceLocation(m_utf16Offset, 0, m_lineNr, m_lineUtf16Offset);
177}
178
179int LineWriter::addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback)
180{
181 int nextId = ++m_lastCallbackId;
182 Q_ASSERT(nextId != 0);
183 if (callback)
184 m_textAddCallbacks.insert(nextId, callback);
185 return nextId;
186}
187
189{
190 return addTextAddCallback([nLines](LineWriter &self, TextAddType t) {
191 if (t == TextAddType::Normal) {
192 quint32 c = self.counter();
193 QString spacesToPreserve;
194 bool spaceOnly = QStringView(self.m_currentLine).trimmed().isEmpty();
195 if (spaceOnly && !self.m_currentLine.isEmpty())
196 spacesToPreserve = self.m_currentLine;
197 self.ensureNewline(nLines, LineWriter::TextAddType::Extra);
198 if (self.counter() != c && !spacesToPreserve.isEmpty())
199 self.write(spacesToPreserve, TextAddType::Extra);
200 return false;
201 } else {
202 return true;
203 }
204 });
205}
206
207void LineWriter::setLineIndent(int indentAmount)
208{
209 int startNonSpace = 0;
210 while (startNonSpace < m_currentLine.size() && m_currentLine.at(startNonSpace).isSpace())
211 ++startNonSpace;
212 int oldColumn = column(startNonSpace);
213 if (indentAmount >= 0) {
214 QString indent;
215 if (m_options.formatOptions.useTabs) {
216 indent = QStringLiteral(u"\t").repeated(indentAmount / m_options.formatOptions.tabSize)
217 + QStringLiteral(u" ").repeated(indentAmount % m_options.formatOptions.tabSize);
218 } else {
219 indent = QStringLiteral(u" ").repeated(indentAmount);
220 }
221 if (indent != m_currentLine.mid(0, startNonSpace)) {
222 quint32 colChange = indentAmount - oldColumn;
223 m_currentColumnNr += colChange;
224 m_currentLine = indent + m_currentLine.mid(startNonSpace);
225 m_currentColumnNr = column(m_currentLine.size());
227 }
228 }
229}
230
232{
233 switch (trailingSpace) {
235 break;
238 while (lastNonSpace > 0 && m_currentLine.at(lastNonSpace - 1).isSpace())
239 --lastNonSpace;
243 column(m_currentLine.size()); // to be extra accurate in the potential split
244 lineChanged();
245 }
246 } break;
247 }
248}
249
250void LineWriter::reindentAndSplit(const QString &eol, bool eof)
251{
252 // maybe write out
253 if (!eol.isEmpty() || eof) {
254 handleTrailingSpace(m_options.codeTrailingSpace);
255 commitLine(eol);
256 }
257}
258
260{
261 return SourceLocation(m_utf16Offset + m_currentLine.size(), 0, m_lineNr,
262 m_lineUtf16Offset + m_currentLine.size());
263}
264
265int LineWriter::column(int index)
266{
267 if (index > m_currentLine.size())
268 index = m_currentLine.size();
269 IndentInfo iInfo(QStringView(m_currentLine).mid(0, index), m_options.formatOptions.tabSize,
270 m_columnNr);
271 return iInfo.column;
272}
273
274void LineWriter::textAddCallback(LineWriter::TextAddType t)
275{
276 if (m_textAddCallbacks.isEmpty())
277 return;
278 int iNow = (--m_textAddCallbacks.end()).key() + 1;
279 while (true) {
280 auto it = m_textAddCallbacks.lowerBound(iNow);
281 if (it == m_textAddCallbacks.begin())
282 break;
283 --it;
284 iNow = it.key();
285 if (!it.value()(*this, t))
286 m_textAddCallbacks.erase(it);
287 }
288}
289
290void LineWriter::commitLine(const QString &eol, TextAddType tType, int untilChar)
291{
292 if (untilChar == -1)
293 untilChar = m_currentLine.size();
294 bool isSpaceOnly = QStringView(m_currentLine).mid(0, untilChar).trimmed().isEmpty();
295 bool isEmptyNewline = !eol.isEmpty() && isSpaceOnly;
296 // update position, lineNr,...
297 // write out
298 for (SinkF &sink : m_innerSinks)
299 sink(m_currentLine.mid(0, untilChar));
300 m_utf16Offset += untilChar;
301 if (!eol.isEmpty()) {
302 m_utf16Offset += eol.size();
303 for (SinkF &sink : m_innerSinks)
304 sink(eol);
305 ++m_lineNr;
306 m_columnNr = 0;
308 } else {
309 m_columnNr = column(untilChar);
310 m_lineUtf16Offset += untilChar;
311 }
312 if (untilChar == m_currentLine.size()) {
314 m_currentLine.clear();
315 } else {
316 QString nextLine = m_currentLine.mid(untilChar);
317 m_currentLine = m_currentLine.mid(0, untilChar);
320 m_currentLine = nextLine;
321 }
323 m_currentColumnNr = column(m_currentLine.size());
324 TextAddType notifyType = tType;
325 switch (tType) {
326 case TextAddType::Normal:
327 if (eol.isEmpty())
328 notifyType = TextAddType::PartialCommit;
329 else
330 notifyType = TextAddType::Newline;
331 break;
332 case TextAddType::Extra:
333 if (eol.isEmpty())
334 notifyType = TextAddType::NewlineExtra;
335 else
336 notifyType = TextAddType::PartialCommit;
337 break;
338 case TextAddType::Newline:
339 case TextAddType::NewlineSplit:
340 case TextAddType::NewlineExtra:
341 case TextAddType::PartialCommit:
342 case TextAddType::Eof:
343 break;
344 }
345 if (isEmptyNewline)
346 ++m_committedEmptyLines;
347 else if (!isSpaceOnly)
348 m_committedEmptyLines = 0;
349 // notify
350 textAddCallback(notifyType);
351}
352
353} // namespace Dom
354} // namespace QQmlJS
355QT_END_NAMESPACE
356
357#include "moc_qqmldomlinewriter_p.cpp"
LineWriter & write(QStringView v, TextAddType tType=TextAddType::Normal)
LineWriter & ensureSemicolon(TextAddType t=TextAddType::Extra)
LineWriter & ensureNewline(int nNewlines=1, TextAddType t=TextAddType::Extra)
LineWriter & ensureSpace(TextAddType t=TextAddType::Extra)
void eof(bool ensureNewline=true)
int addNewlinesAutospacerCallback(int nLines)
virtual void reindentAndSplit(const QString &eol, bool eof=false)
void commitLine(const QString &eol, TextAddType t=TextAddType::Normal, int untilChar=-1)
SourceLocation currentSourceLocation() const
LineWriter & ensureSpace(QStringView space, TextAddType t=TextAddType::Extra)
SourceLocation committedLocation() const
void setLineIndent(int indentAmount)
int addTextAddCallback(std::function< bool(LineWriter &, TextAddType)> callback)
void textAddCallback(TextAddType t)
LineWriter(const SinkF &innerSink, const QString &fileName, const LineWriterOptions &options=LineWriterOptions(), int lineNr=0, int columnNr=0, int utf16Offset=0, const QString &currentLine=QString())
static QRegularExpressionMatch matchHelper(QRegularExpression &re, String &&s, Args &&...args)