File indexing completed on 2024-04-14 03:55:48

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 "commandrangeexpressionparser.h"
0011 
0012 #include "katedocument.h"
0013 #include "kateview.h"
0014 #include "marks.h"
0015 #include <vimode/inputmodemanager.h>
0016 
0017 #include <QRegularExpression>
0018 #include <QStringList>
0019 
0020 using namespace KateVi;
0021 
0022 #define RegExp(name, pattern)                                                                                                                                  \
0023     inline const QRegularExpression &name()                                                                                                                    \
0024     {                                                                                                                                                          \
0025         static const QRegularExpression regex(QStringLiteral(pattern));                                                                                        \
0026         return regex;                                                                                                                                          \
0027     }
0028 
0029 namespace
0030 {
0031 #define RE_MARK "\\'[0-9a-z><\\+\\*\\_]"
0032 #define RE_THISLINE "\\."
0033 #define RE_LASTLINE "\\$"
0034 #define RE_LINE "\\d+"
0035 #define RE_FORWARDSEARCH "/[^/]*/?"
0036 #define RE_BACKWARDSEARCH "\\?[^?]*\\??"
0037 #define RE_BASE "(?:" RE_MARK ")|(?:" RE_LINE ")|(?:" RE_THISLINE ")|(?:" RE_LASTLINE ")|(?:" RE_FORWARDSEARCH ")|(?:" RE_BACKWARDSEARCH ")"
0038 #define RE_OFFSET "[+-](?:" RE_BASE ")?"
0039 #define RE_POSITION "(" RE_BASE ")((?:" RE_OFFSET ")*)"
0040 
0041 RegExp(RE_Line, RE_LINE) RegExp(RE_LastLine, RE_LASTLINE) RegExp(RE_ThisLine, RE_THISLINE) RegExp(RE_Mark, RE_MARK) RegExp(RE_ForwardSearch, "^/([^/]*)/?$")
0042     RegExp(RE_BackwardSearch, "^\\?([^?]*)\\??$") RegExp(RE_CalculatePositionSplit, "[-+](?!([+-]|$))")
0043     // The range regexp contains seven groups: the first is the start position, the second is
0044     // the base of the start position, the third is the offset of the start position, the
0045     // fourth is the end position including a leading comma, the fifth is end position
0046     // without the comma, the sixth is the base of the end position, and the seventh is the
0047     // offset of the end position. The third and fourth groups may be empty, and the
0048     // fifth, sixth and seventh groups are contingent on the fourth group.
0049     inline const QRegularExpression &RE_CmdRange()
0050 {
0051     static const QRegularExpression regex(QStringLiteral("^(" RE_POSITION ")((?:,(" RE_POSITION "))?)"));
0052     return regex;
0053 }
0054 }
0055 
0056 CommandRangeExpressionParser::CommandRangeExpressionParser(InputModeManager *vimanager)
0057     : m_viInputModeManager(vimanager)
0058 {
0059 }
0060 
0061 QString CommandRangeExpressionParser::parseRangeString(const QString &command)
0062 {
0063     if (command.isEmpty()) {
0064         return QString();
0065     }
0066 
0067     if (command.at(0) == QLatin1Char('%')) {
0068         return QStringLiteral("%");
0069     }
0070 
0071     QRegularExpressionMatch rangeMatch = RE_CmdRange().match(command);
0072 
0073     return rangeMatch.hasMatch() ? rangeMatch.captured() : QString();
0074 }
0075 
0076 KTextEditor::Range CommandRangeExpressionParser::parseRange(const QString &command, QString &destTransformedCommand) const
0077 {
0078     if (command.isEmpty()) {
0079         return KTextEditor::Range::invalid();
0080     }
0081 
0082     QString commandTmp = command;
0083 
0084     // expand '%' to '1,$' ("all lines") if at the start of the line
0085     if (commandTmp.at(0) == QLatin1Char('%')) {
0086         commandTmp.replace(0, 1, QStringLiteral("1,$"));
0087     }
0088 
0089     QRegularExpressionMatch rangeMatch = RE_CmdRange().match(commandTmp);
0090 
0091     if (!rangeMatch.hasMatch()) {
0092         return KTextEditor::Range::invalid();
0093     }
0094 
0095     QString position_string1 = rangeMatch.captured(1);
0096     QString position_string2 = rangeMatch.captured(4);
0097     int position1 = calculatePosition(position_string1);
0098     int position2 = (position_string2.isEmpty()) ? position1 : calculatePosition(rangeMatch.captured(5));
0099 
0100     commandTmp.remove(RE_CmdRange());
0101 
0102     // Vi indexes lines starting from 1; however, it does accept 0 as a valid line index
0103     // and treats it as 1
0104     position1 = (position1 == 0) ? 1 : position1;
0105     position2 = (position2 == 0) ? 1 : position2;
0106 
0107     // special case: if the command is just a number with an optional +/- prefix, rewrite to "goto"
0108     if (commandTmp.isEmpty()) {
0109         destTransformedCommand = QStringLiteral("goto %1").arg(position1);
0110         return KTextEditor::Range::invalid();
0111     } else {
0112         destTransformedCommand = commandTmp;
0113         return KTextEditor::Range(KTextEditor::Range(position1 - 1, 0, position2 - 1, 0));
0114     }
0115 }
0116 
0117 int CommandRangeExpressionParser::calculatePosition(const QString &string) const
0118 {
0119     int pos = 0;
0120     QList<bool> operators_list;
0121     const QStringList split = string.split(RE_CalculatePositionSplit());
0122     QList<int> values;
0123 
0124     for (const QString &line : split) {
0125         pos += line.size();
0126 
0127         if (pos < string.size()) {
0128             if (string.at(pos) == QLatin1Char('+')) {
0129                 operators_list.push_back(true);
0130             } else if (string.at(pos) == QLatin1Char('-')) {
0131                 operators_list.push_back(false);
0132             } else {
0133                 Q_ASSERT(false);
0134             }
0135         }
0136 
0137         ++pos;
0138 
0139         matchLineNumber(line, values) || matchLastLine(line, values) || matchThisLine(line, values) || matchMark(line, values)
0140             || matchForwardSearch(line, values) || matchBackwardSearch(line, values);
0141     }
0142 
0143     if (values.isEmpty()) {
0144         return -1;
0145     }
0146 
0147     int result = values.at(0);
0148     for (int i = 0; i < operators_list.size(); ++i) {
0149         if (operators_list.at(i)) {
0150             result += values.at(i + 1);
0151         } else {
0152             result -= values.at(i + 1);
0153         }
0154     }
0155 
0156     return result;
0157 }
0158 
0159 bool CommandRangeExpressionParser::matchLineNumber(const QString &line, QList<int> &values)
0160 {
0161     QRegularExpressionMatch match = RE_Line().match(line);
0162 
0163     if (!match.hasMatch() || match.capturedLength() != line.length()) {
0164         return false;
0165     }
0166 
0167     values.push_back(line.toInt());
0168     return true;
0169 }
0170 
0171 bool CommandRangeExpressionParser::matchLastLine(const QString &line, QList<int> &values) const
0172 {
0173     QRegularExpressionMatch match = RE_LastLine().match(line);
0174 
0175     if (!match.hasMatch() || match.capturedLength() != line.length()) {
0176         return false;
0177     }
0178 
0179     values.push_back(m_viInputModeManager->view()->doc()->lines());
0180     return true;
0181 }
0182 
0183 bool CommandRangeExpressionParser::matchThisLine(const QString &line, QList<int> &values) const
0184 {
0185     QRegularExpressionMatch match = RE_ThisLine().match(line);
0186 
0187     if (!match.hasMatch() || match.capturedLength() != line.length()) {
0188         return false;
0189     }
0190 
0191     values.push_back(m_viInputModeManager->view()->cursorPosition().line() + 1);
0192     return true;
0193 }
0194 
0195 bool CommandRangeExpressionParser::matchMark(const QString &line, QList<int> &values) const
0196 {
0197     QRegularExpressionMatch match = RE_Mark().match(line);
0198 
0199     if (!match.hasMatch() || match.capturedLength() != line.length()) {
0200         return false;
0201     }
0202 
0203     values.push_back(m_viInputModeManager->marks()->getMarkPosition(line.at(1)).line() + 1);
0204     return true;
0205 }
0206 
0207 bool CommandRangeExpressionParser::matchForwardSearch(const QString &line, QList<int> &values) const
0208 {
0209     QRegularExpressionMatch match = RE_ForwardSearch().match(line);
0210 
0211     if (!match.hasMatch()) {
0212         return false;
0213     }
0214 
0215     QString pattern = match.captured(1);
0216     KTextEditor::Range range(m_viInputModeManager->view()->cursorPosition(), m_viInputModeManager->view()->doc()->documentEnd());
0217     QList<KTextEditor::Range> matchingLines = m_viInputModeManager->view()->doc()->searchText(range, pattern, KTextEditor::Regex);
0218 
0219     if (matchingLines.isEmpty()) {
0220         return true;
0221     }
0222 
0223     int lineNumber = matchingLines.first().start().line();
0224 
0225     values.push_back(lineNumber + 1);
0226     return true;
0227 }
0228 
0229 bool CommandRangeExpressionParser::matchBackwardSearch(const QString &line, QList<int> &values) const
0230 {
0231     QRegularExpressionMatch match = RE_BackwardSearch().match(line);
0232 
0233     if (!match.hasMatch()) {
0234         return false;
0235     }
0236 
0237     QString pattern = match.captured(1);
0238     KTextEditor::Range range(KTextEditor::Cursor(0, 0), m_viInputModeManager->view()->cursorPosition());
0239     QList<KTextEditor::Range> matchingLines = m_viInputModeManager->view()->doc()->searchText(range, pattern, KTextEditor::Regex);
0240 
0241     if (matchingLines.isEmpty()) {
0242         return true;
0243     }
0244 
0245     int lineNumber = matchingLines.first().start().line();
0246 
0247     values.push_back(lineNumber + 1);
0248     return true;
0249 }