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 }