File indexing completed on 2024-04-21 03:57:47

0001 /*
0002     SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
0003     SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
0004     SPDX-FileCopyrightText: 2012 Vegard Øye
0005     SPDX-FileCopyrightText: 2013 Simon St James <kdedevel@etotheipiplusone.com>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "katecommandrangeexpressionparser.h"
0011 
0012 #include "katedocument.h"
0013 #include "kateview.h"
0014 
0015 #include <QStringList>
0016 
0017 using KTextEditor::Cursor;
0018 using KTextEditor::Range;
0019 
0020 CommandRangeExpressionParser::CommandRangeExpressionParser()
0021 {
0022     m_line = QStringLiteral("\\d+");
0023     m_lastLine = QStringLiteral("\\$");
0024     m_thisLine = QStringLiteral("\\.");
0025 
0026     m_forwardSearch = QStringLiteral("/([^/]*)/?");
0027     m_forwardSearch2 = QStringLiteral("/[^/]*/?"); // no group
0028     m_backwardSearch = QStringLiteral("\\?([^?]*)\\??");
0029     m_backwardSearch2 = QStringLiteral("\\?[^?]*\\??"); // no group
0030     m_base = QLatin1String("(?:%1)").arg(m_mark) + QLatin1String("|(?:%1)").arg(m_line) + QLatin1String("|(?:%1)").arg(m_thisLine)
0031         + QLatin1String("|(?:%1)").arg(m_lastLine) + QLatin1String("|(?:%1)").arg(m_forwardSearch2) + QLatin1String("|(?:%1)").arg(m_backwardSearch2);
0032     m_offset = QLatin1String("[+-](?:%1)?").arg(m_base);
0033 
0034     // The position regexp contains two groups: the base and the offset.
0035     // The offset may be empty.
0036     m_position = QStringLiteral("(%1)((?:%2)*)").arg(m_base, m_offset);
0037 
0038     // The range regexp contains seven groups: the first is the start position, the second is
0039     // the base of the start position, the third is the offset of the start position, the
0040     // fourth is the end position including a leading comma, the fifth is end position
0041     // without the comma, the sixth is the base of the end position, and the seventh is the
0042     // offset of the end position. The third and fourth groups may be empty, and the
0043     // fifth, sixth and seventh groups are contingent on the fourth group.
0044     m_cmdRangeRegex.setPattern(QStringLiteral("^(%1)((?:,(%1))?)").arg(m_position));
0045 }
0046 
0047 Range CommandRangeExpressionParser::parseRangeExpression(const QString &command,
0048                                                          KTextEditor::ViewPrivate *view,
0049                                                          QString &destRangeExpression,
0050                                                          QString &destTransformedCommand)
0051 {
0052     CommandRangeExpressionParser rangeExpressionParser;
0053     return rangeExpressionParser.parseRangeExpression(command, destRangeExpression, destTransformedCommand, view);
0054 }
0055 
0056 Range CommandRangeExpressionParser::parseRangeExpression(const QString &command,
0057                                                          QString &destRangeExpression,
0058                                                          QString &destTransformedCommand,
0059                                                          KTextEditor::ViewPrivate *view)
0060 {
0061     Range parsedRange(0, -1, 0, -1);
0062     if (command.isEmpty()) {
0063         return parsedRange;
0064     }
0065     QString commandTmp = command;
0066     bool leadingRangeWasPercent = false;
0067     // expand '%' to '1,$' ("all lines") if at the start of the line
0068     if (commandTmp.at(0) == QLatin1Char('%')) {
0069         commandTmp.replace(0, 1, QStringLiteral("1,$"));
0070         leadingRangeWasPercent = true;
0071     }
0072 
0073     const auto match = m_cmdRangeRegex.match(commandTmp);
0074     if (match.hasMatch() && match.capturedLength(0) > 0) {
0075         commandTmp.remove(m_cmdRangeRegex);
0076 
0077         const QString position_string1 = match.captured(1);
0078         QString position_string2 = match.captured(4);
0079         int position1 = calculatePosition(position_string1, view);
0080 
0081         int position2;
0082         if (!position_string2.isEmpty()) {
0083             // remove the comma
0084             position_string2 = match.captured(5);
0085             position2 = calculatePosition(position_string2, view);
0086         } else {
0087             position2 = position1;
0088         }
0089 
0090         // special case: if the command is just a number with an optional +/- prefix, rewrite to "goto"
0091         if (commandTmp.isEmpty()) {
0092             commandTmp = QStringLiteral("goto %1").arg(position1);
0093         } else {
0094             parsedRange.setRange(KTextEditor::Range(position1 - 1, 0, position2 - 1, 0));
0095         }
0096 
0097         destRangeExpression = leadingRangeWasPercent ? QStringLiteral("%") : match.captured(0);
0098         destTransformedCommand = commandTmp;
0099     }
0100 
0101     return parsedRange;
0102 }
0103 
0104 int CommandRangeExpressionParser::calculatePosition(const QString &string, KTextEditor::ViewPrivate *view)
0105 {
0106     int pos = 0;
0107     std::vector<bool> operators_list;
0108     const QStringList split = string.split(QRegularExpression(QStringLiteral("[-+](?!([+-]|$))")));
0109     std::vector<int> values;
0110 
0111     for (const QString &line : split) {
0112         pos += line.size();
0113 
0114         if (pos < string.size()) {
0115             if (string.at(pos) == QLatin1Char('+')) {
0116                 operators_list.push_back(true);
0117             } else if (string.at(pos) == QLatin1Char('-')) {
0118                 operators_list.push_back(false);
0119             } else {
0120                 Q_ASSERT(false);
0121             }
0122         }
0123 
0124         ++pos;
0125 
0126         static const auto lineRe = QRegularExpression(QRegularExpression::anchoredPattern(m_line));
0127         static const auto lastLineRe = QRegularExpression(QRegularExpression::anchoredPattern(m_lastLine));
0128         static const auto thisLineRe = QRegularExpression(QRegularExpression::anchoredPattern(m_thisLine));
0129         static const auto forwardSearchRe = QRegularExpression(QRegularExpression::anchoredPattern(m_forwardSearch));
0130         static const auto backwardSearchRe = QRegularExpression(QRegularExpression::anchoredPattern(m_backwardSearch));
0131 
0132         QRegularExpressionMatch rmatch;
0133         if (lineRe.match(line).hasMatch()) {
0134             values.push_back(line.toInt());
0135         } else if (lastLineRe.match(line).hasMatch()) {
0136             values.push_back(view->doc()->lines());
0137         } else if (thisLineRe.match(line).hasMatch()) {
0138             values.push_back(view->cursorPosition().line() + 1);
0139         } else if (line.contains(forwardSearchRe, &rmatch)) {
0140             const QString pattern = rmatch.captured(1);
0141             const int matchLine =
0142                 view->doc()->searchText(Range(view->cursorPosition(), view->doc()->documentEnd()), pattern, KTextEditor::Regex).first().start().line();
0143             values.push_back((matchLine < 0) ? -1 : matchLine + 1);
0144         } else if (line.contains(backwardSearchRe, &rmatch)) {
0145             const QString pattern = rmatch.captured(1);
0146             const int matchLine = view->doc()->searchText(Range(Cursor(0, 0), view->cursorPosition()), pattern, KTextEditor::Regex).first().start().line();
0147             values.push_back((matchLine < 0) ? -1 : matchLine + 1);
0148         }
0149     }
0150 
0151     if (values.empty()) {
0152         return -1;
0153     }
0154 
0155     int result = values.at(0);
0156     for (size_t i = 0; i < operators_list.size(); ++i) {
0157         if (operators_list.at(i) == true) {
0158             result += values.at(i + 1);
0159         } else {
0160             result -= values.at(i + 1);
0161         }
0162     }
0163 
0164     return result;
0165 }