// Copyright (C) 2022 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 "qqmldomindentinglinewriter_p.h" #include "qqmldomlinewriter_p.h" #include #include QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { // If one of these tokens is the split token on a line, the line should be split after it. static constexpr bool shouldSplitAfterToken(int kind) { switch (kind) { case Lexer::T_COMMA: case Lexer::T_COLON: case Lexer::T_SEMICOLON: case Lexer::T_LBRACE: case Lexer::T_LPAREN: case Lexer::T_LBRACKET: return true; default: break; } return false; } FormatPartialStatus &IndentingLineWriter::fStatus() { if (!m_fStatusValid) { m_fStatus = formatCodeLine(m_currentLine, m_options.formatOptions, m_preCachedStatus); m_fStatusValid = true; } return m_fStatus; } void IndentingLineWriter::willCommit() { m_preCachedStatus = fStatus().currentStatus; } void IndentingLineWriter::reindentAndSplit(const QString &eol, bool eof) { // maybe re-indent if (m_reindent && m_columnNr == 0) setLineIndent(fStatus().indentLine()); if (!eol.isEmpty() || eof) handleTrailingSpace(); // maybe split long line if (m_options.maxLineLength > 0 && m_currentLine.size() > m_options.maxLineLength) splitOnMaxLength(eol, eof); // maybe write out if (!eol.isEmpty() || eof) commitLine(eol); } void IndentingLineWriter::handleTrailingSpace() { LineWriterOptions::TrailingSpace trailingSpace; if (!m_currentLine.isEmpty() && m_currentLine.trimmed().isEmpty()) { // space only line const Scanner::State &oldState = m_preCachedStatus.lexerState; if (oldState.isMultilineComment()) trailingSpace = m_options.commentTrailingSpace; else if (oldState.isMultiline()) trailingSpace = m_options.stringTrailingSpace; else trailingSpace = m_options.codeTrailingSpace; // in the LSP we will probably want to treat is specially if it is the line with the // cursor, of if indentation of it is requested } else { const Scanner::State ¤tState = fStatus().currentStatus.lexerState; if (currentState.isMultilineComment()) { trailingSpace = m_options.commentTrailingSpace; } else if (currentState.isMultiline()) { trailingSpace = m_options.stringTrailingSpace; } else { const int kind = (fStatus().lineTokens.isEmpty() ? Lexer::T_EOL : fStatus().lineTokens.last().lexKind); if (Token::lexKindIsComment(kind)) { // a // comment... trailingSpace = m_options.commentTrailingSpace; Q_ASSERT(fStatus().currentStatus.state().type != FormatTextStatus::StateType::MultilineCommentCont && fStatus().currentStatus.state().type != FormatTextStatus::StateType:: MultilineCommentStart); // these should have been // handled above } else { trailingSpace = m_options.codeTrailingSpace; } } } LineWriter::handleTrailingSpace(trailingSpace); } int IndentingLineWriter::findSplitLocation(const QList &tokens, int minSplitLength) { // Reverse search to find the "last" auto lastDelimiterTokenBeforeLineSplitIt = std::find_if(tokens.crbegin(), tokens.crend(), [&](auto &&t) { return Token::lexKindIsDelimiter(t.lexKind) && column(t.end()) >= minSplitLength; }); if (lastDelimiterTokenBeforeLineSplitIt == tokens.crend()) return -1; if (shouldSplitAfterToken(lastDelimiterTokenBeforeLineSplitIt->lexKind)) { return lastDelimiterTokenBeforeLineSplitIt->end(); } else { // split before the token (after the previous token) auto previousTokenIt = std::next(lastDelimiterTokenBeforeLineSplitIt); return previousTokenIt != tokens.crend() ? previousTokenIt->end() : lastDelimiterTokenBeforeLineSplitIt->begin(); } } void IndentingLineWriter::splitOnMaxLength(const QString &eol, bool eof) { if (fStatus().lineTokens.size() <= 1) return; // {}[] should already be handled (handle also here?) int minLen = 0; while (minLen < m_currentLine.size() && m_currentLine.at(minLen).isSpace()) ++minLen; minLen = column(minLen) + m_options.minContentLength; int possibleSplit = findSplitLocation(fStatus().lineTokens, minLen); if (possibleSplit > 0) { lineChanged(); commitLine(eolToWrite(), TextAddType::NewlineSplit, possibleSplit); setReindent(true); reindentAndSplit(eol, eof); } } } // namespace Dom } // namespace QQmlJS QT_END_NAMESPACE